Title
packaged_task::operator= should abandon its shared state
Status
new
Section
[futures.task.members]
Submitter
Jonathan Wakely

Created on 2024-09-19.00:00:00 last changed 2 weeks ago

Messages

Date: 2024-10-03.09:46:29

Proposed resolution:

This wording is relative to N4988.

  1. Modify [futures.promise] as indicated:

    promise& operator=(promise&& rhs) noexcept;
    

    -9- Effects: Abandons any shared state ([futures.state]) and then as if Equivalent to `promise(std::move(rhs)).swap(*this)`.

    -10- Returns: `*this`.

  2. Modify [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases any shared state ([futures.state]);
    2. (11.2) — calls Equivalent to `packaged_task(std::move(rhs)).swap(*this)`.

    -?- Returns: `*this`.

Date: 2024-10-15.00:00:00

[ 2024-10-02; LWG telecon ]

Agreed to change `promise` the same way, which is safe for self-assignment and matches what all three of libstdc++, libc++ and MSVC do today. Ask SG1 to review.

Date: 2024-10-15.00:00:00

[ 2024-10-02; Jonathan provides improved wording ]

Following reflector discussion, remove the "Releases any shared state" text completely. The remaining effects imply that the state will be abandoned anyway. This makes it safe against self-assignment.

Date: 2024-10-15.00:00:00

[ 2024-10-02; Reflector poll ]

Set priority to 3 after reflector poll.

This wording is relative to N4988.

  1. Modify [futures.task.members] as indicated:

    packaged_task& operator=(packaged_task&& rhs) noexcept;
    

    -11- Effects:

    1. (11.1) — Releases Abandons any shared state ([futures.state]);
    2. (11.2) — calls `packaged_task(std::move(rhs)).swap(*this)`.

    -?- Returns: `*this`.

Date: 2024-09-19.00:00:00

The `packaged_task` move assignment operator is specified to release the previous shared state. This means it releases ownership, but does not make the shared state ready. Any future that shares ownership of the shared state will never receive a result, because the provider is gone. This means that any thread that waits on the future will block forever.

There is a note on `packaged_task::reset()` which claims that assignment abandons the state, which is not supported by any normative wording:

void reset();

-26- Effects: As if `*this = packaged_task(std::move(f))`, where `f` is the task stored in `*this`.

[Note 2: This constructs a new shared state for `*this`. The old state is abandoned ([futures.state]). — end note]

Presumably, the intended behaviour of assignment was to abandon the shared state, i.e. make it ready with a `broken_promise` error, and then release it. That is what the `std::promise` move assignment does (see [futures.promise] p9). Both libstdc++ and libc++ abandon the state, despite what the standard says.

History
Date User Action Args
2024-10-02 16:29:29adminsetmessages: + msg14410
2024-10-02 11:50:49adminsetmessages: + msg14399
2024-10-02 11:50:49adminsetmessages: + msg14398
2024-09-20 12:26:44adminsetmessages: + msg14391
2024-09-19 00:00:00admincreate