1507. promise::XXX_at_thread_exit functions have no synchronization requirements

Section: 33.6.6 [futures.promise] Status: Resolved Submitter: INCITS Opened: 2010-08-25 Last modified: 2016-02-10

Priority: Not Prioritized

View other active issues in [futures.promise].

View all other issues in [futures.promise].

View all issues with Resolved status.

Discussion:

Addresses US-199

promise::XXX_at_thread_exit functions have no synchronization requirements. Specifying synchronization for these member functions requires coordinating with the words in 33.6.6 [futures.promise]/21 and 25, which give synchronization requirements for promise::set_value and promise::set_exception (33.6.6 [futures.promise] p. 26 ff., p. 29 ff.).

[ Resolution proposed by ballot comment: ]

Change 33.6.6 [futures.promise]/21 to mention set_value_at_thread_exit and set_exception_at_thread_exit; with this text, replace 33.6.6 [futures.promise]/25 and add two new paragraphs, after 33.6.6 [futures.promise]/28 and 33.6.6 [futures.promise]/31.

[2011-03-8: Lawrence comments and drafts wording]

This comment applies as well to other *_at_thread_exit functions. The following resolution adds synchronization paragraphs to all of them and edits a couple of related synchronization paragraphs.

[2011-03-09: Hans and Anthony add some improvements]

[2011-03-19: Detlef comments]

In regard to the suggested part:

These operations do not provide any ordering guarantees with respect to other operations, except through operations on futures that reference the same shared state.

I would like this to change to:

These operations do not provide any ordering guarantees with respect to other operations on the same promise object. [Note: They synchronize with calls to operations on objects that refer to the same shared state according to 33.6.5 [futures.state]. — end note]

The current proposed resolution has exactly the same paragraph at for places. I propose to have it only once as new paragraph 2.

This also covers 1504 (US-196) and 1505 (US-197). US-197 is essentially rejected with this resolution, but a clarification is added that the normative wording is already in 33.6.5 [futures.state].

Proposed Resolution

  1. Edit 33.4.3.2 [thread.mutex.requirements.mutex] paragraph 5 as follows:

    5 The implementation shall provide lock and unlock operations, as described below. The implementation shall serialize those operations.For purposes of determining the existence of a data race, these behave as atomic operations (6.8.2 [intro.multithread]). The lock and unlock operations on a single mutex shall appear to occur in a single total order. [Note: this can be viewed as the modification order (6.8.2 [intro.multithread]) of the mutex. — end note] [ Note: Construction and destruction of an object of a mutex type need not be thread-safe; other synchronization should be used to ensure that mutex objects are initialized and visible to other threads. — end note ]

  2. Edit 33.5 [thread.condition] paragraphs 6-9 as follows:

    void notify_all_at_thread_exit(condition_variable& cond, unique_lock<mutex> lk);
    

    -6- Requires: lk is locked by the calling thread and either

    • no other thread is waiting on cond, or
    • lk.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_for, or wait_until) threads.

    -7- Effects: transfers ownership of the lock associated with lk into internal storage and schedules cond to be notified when the current thread exits, after all objects of thread storage duration associated with the current thread have been destroyed. This notification shall be as if

    lk.unlock();
    cond.notify_all();
    

    -?- Synchronization: The call to notify_all_at_thread_exit and the completion of the destructors for all the current thread's variables of thread storage duration synchronize with (6.8.2 [intro.multithread]) calls to functions waiting on cond.

    -8- Note: The supplied lock will be held until the thread exits, and care must be taken to ensure that this does not cause deadlock due to lock ordering issues. After calling notify_all_at_thread_exit it is recommended that the thread should be exited as soon as possible, and that no blocking or time-consuming tasks are run on that thread.

    -9- Note: It is the user's responsibility to ensure that waiting threads do not erroneously assume that the thread has finished if they experience spurious wakeups. This typically requires that the condition being waited for is satisfied while holding the lock on lk, and that this lock is not released and reacquired prior to calling notify_all_at_thread_exit.

  3. Edit 33.6.6 [futures.promise], paragraphs 14-27 as follows:

    void promise::set_value(const R& r);
    void promise::set_value(R&& r);
    void promise<R&>::set_value(R& r);
    void promise<void>::set_value();
    

    -14- Effects: atomically stores the value r in the shared state and makes that state ready (33.6.5 [futures.state]).

    -15- Throws:

    • future_error if its shared state already has a stored value or exception, or
    • for the first version, any exception thrown by the copy constructor of R, or
    • for the second version, any exception thrown by the move constructor of R.

    -16- Error conditions:

    • promise_already_satisfied if its shared state already has a stored value or exception.
    • no_state if *this has no shared state.

    -17- Synchronization: calls to set_value and set_exception on a single promise object are serialized. [ Note: And they synchronize and serialize with other functions through the referred shared state. — end note ]For purposes of determining the existence of a data race, set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit behave as atomic operations (6.8.2 [intro.multithread]) on the memory location associated with the promise. Calls to these operations on a single promise shall appear to occur in a single total order. [Note: this can be viewed as the modification order (6.8.2 [intro.multithread]) of the promise. — end note] These operations do not provide any ordering guarantees with respect to other operations, except through operations on futures that reference the same shared state.

    void set_exception(exception_ptr p);
    

    -18- Effects: atomically stores the exception pointer p in the shared state and makes that state ready (33.6.5 [futures.state]).

    -19- Throws: future_error if its shared state already has a stored value or exception.

    -20- Error conditions:

    • promise_already_satisfied if its shared state already has a stored value or exception.
    • no_state if *this has no shared state.

    -21- Synchronization: calls to set_value and set_exception on a single promise object are serialized. [ Note: And they synchronize and serialize with other functions through the referred shared state. — end note ]For purposes of determining the existence of a data race, set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit behave as atomic operations (6.8.2 [intro.multithread]) on the memory location associated with the promise. Calls to these operations on a single promise shall appear to occur in a single total order. [Note: this can be viewed as the modification order (6.8.2 [intro.multithread]) of the promise. — end note] These operations do not provide any ordering guarantees with respect to other operations, except through operations on futures that reference the same shared state.

    void promise::set_value_at_thread_exit(const R& r);
    void promise::set_value_at_thread_exit(R&& r);
    void promise<R&>::set_value_at_thread_exit(R& r);
    void promise<void>::set_value_at_thread_exit();
    

    -22- Effects: Stores the value r in the shared state without making that state ready immediately. Schedules that state to be made ready when the current thread exits, after all objects of thread storage duration associated with the current thread have been destroyed.

    -23- Throws: future_error if an error condition occurs.

    -24- Error conditions:

    • promise_already_satisfied if its shared state already has a stored value or exception.
    • no_state if *this has no shared state.

    -??- Synchronization: For purposes of determining the existence of a data race, set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit behave as atomic operations (6.8.2 [intro.multithread]) on the memory location associated with the promise. Calls to these operations on a single promise shall appear to occur in a single total order. [Note: this can be viewed as the modification order (6.8.2 [intro.multithread]) of the promise. — end note] These operations do not provide any ordering guarantees with respect to other operations, except through operations on futures that reference the same shared state.

    void promise::set_exception_at_thread_exit(exception_ptr p);
    

    -25- Effects: Stores the exception pointer p in the shared state without making that state ready immediately. Schedules that state to be made ready when the current thread exits, after all objects of thread storage duration associated with the current thread have been destroyed.

    -26- Throws: future_error if an error condition occurs.

    -27- Error conditions:

    • promise_already_satisfied if its shared state already has a stored value or exception.
    • no_state if *this has no shared state.

    -??- Synchronization: For purposes of determining the existence of a data race, set_value, set_exception, set_value_at_thread_exit, and set_exception_at_thread_exit behave as atomic operations (6.8.2 [intro.multithread]) on the memory location associated with the promise. Calls to these operations on a single promise shall appear to occur in a single total order. [Note: this can be viewed as the modification order (6.8.2 [intro.multithread]) of the promise. — end note] These operations do not provide any ordering guarantees with respect to other operations, except through operations on futures that reference the same shared state.

  4. Edit 33.6.10.1 [futures.task.members], paragraph 15-21 as follows:

    void operator()(ArgTypes... args);
    

    -15- Effects: INVOKE(f, t1, t2, ..., tN, R), where f is the stored task of *this and t1, t2, ..., tN are the values in args.... If the task returns normally, the return value is stored as the asynchronous result in the shared state of *this, otherwise the exception thrown by the task is stored. The shared state of *this is made ready, and any threads blocked in a function waiting for the shared state of *this to become ready are unblocked.

    -16- Throws: a future_error exception object if there is no shared state or the stored task has already been invoked.

    -17- Error conditions:

    • promise_already_satisfied if the shared state is already ready.
    • no_state if *this has no shared state.

    -18- Synchronization: a successful call to operator() synchronizes with (6.8.2 [intro.multithread]) a call to any member function of a future or shared_future object that shares the shared state of *this. The completion of the invocation of the stored task and the storage of the result (whether normal or exceptional) into the shared state synchronizes with (6.8.2 [intro.multithread]) the successful return from any member function that detects that the state is set to ready. [ Note: operator() synchronizes and serializes with other functions through the shared state. — end note ]

    void make_ready_at_thread_exit(ArgTypes... args);
    

    -19- Effects: INVOKE(f, t1, t2, ..., tN, R), where f is the stored task and t1, t2, ..., tN are the values in args.... If the task returns normally, the return value is stored as the asynchronous result in the shared state of *this, otherwise the exception thrown by the task is stored. In either case, this shall be done without making that state ready (33.6.5 [futures.state]) immediately. Schedules the shared state to be made ready when the current thread exits, after all objects of thread storage duration associated with the current thread have been destroyed.

    -20- Throws: future_error if an error condition occurs.

    -21- Error conditions:

    • promise_already_satisfied if the shared state already has a stored value or exception.
    • no_state if *this has no shared state.

    -??- Synchronization: a successful call to make_ready_at_thread_exit synchronizes with (6.8.2 [intro.multithread]) a call to any member function of a future or shared_future object that shares the shared state of *this. The completion of

    • the invocation of the stored task and the storage of the result (whether normal or exceptional) into the shared state

    • the destructors for all the current thread's variables of thread storage duration

    synchronize with (6.8.2 [intro.multithread]) the successful return from any member function that detects that the state is set to ready. [Note: make_ready_at_thread_exit synchronizes and serializes with other functions through the shared state. — end note]

Proposed resolution:

Resolved 2011-03 Madrid meeting by paper N3278