This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++14 status.

### 2135. Unclear requirement for exceptions thrown in `condition_variable::wait()`

Section: 33.7.4 [thread.condition.condvar], 33.7.5 [thread.condition.condvarany] Status: C++14 Submitter: Pete Becker Opened: 2012-03-06 Last modified: 2015-10-03

Priority: Not Prioritized

View all other issues in [thread.condition.condvar].

View all issues with C++14 status.

Discussion:

`condition_varible::wait()` (and, presumably, `condition_variable_any::wait()`, although I haven't looked at it) says that it calls `lock.unlock()`, and if `condition_variable::wait()` exits by an exception it calls `lock.lock()` on the way out. But if the initial call to `lock.unlock()` threw an exception, does it make sense to call `lock.lock()`? We simply don't know the state of that lock object, and it's probably better not to touch it.

That aside, once the `wait()` call has been unblocked, it calls `lock.lock()`. If `lock.lock()` throws an exception, what happens? The requirement is:

> If the function exits via an exception, `lock.lock()` shall be called prior to exiting the function scope.

That can be read in two different ways. One way is as if it said "`lock.lock()` shall have been called …", i.e. the original, failed, call to `lock.lock()` is all that's required. But a more natural reading is that wait has to call `lock.lock()` again, even though it already failed.

I think this wording suffers from being too general. There are two possible exception sources: the initial call to `lock.unlock()` and the final call to `lock.lock()`. Each one should have its own requirement. Lumping them together muddles things.

[2012, Portland: move to Open]

Pablo: `unlock` failing is easy -- the call leaves it locked. The second case, trying to `lock` fails -- what can you do? This is an odd state as we had it locked before was called wait. Maybe we should call `terminate` as we cannot meet the post-conditions. We could throw a different exception.

Hans: calling `terminate` makes sense as we're likely to call it soon anyway and at least we have some context.

Detlef: what kind of locks might be being used?

Pablo: condition variables are 'our' locks so this is less of a problem. `condition_variable_any` might be more problematic.

The general direction is to call `terminate` if the lock cannot be reacquired.

Pablo: Can we change the wording to 'leaves the mutex locked' ?

Hans: so if the `unlock` throws we simply propagate the exception.

Move the issue to open and add some formal wording at a later time.

[2013-09 Chicago: Resolved]

Detlef improves wording. Daniel suggests to introduce a Remarks element for the special "If the function fails to meet the postcondition..." wording and applies this to the proposed wording.

Proposed resolution:

This wording is relative to N3691.

1. Edit 33.7.4 [thread.condition.condvar] as indicated:

   >     void wait(unique_lock<mutex>& lock);

   > > […]

   > > -10- Effects:

   > > - Atomically calls `lock.unlock()` and blocks on `*this`.

   > > - When unblocked, calls `lock.lock()` (possibly blocking on the lock), then returns.

   > > - The function will unblock when signaled by a call to `notify_one()` or a call to `notify_all()`, or spuriously.

   > > - If the function exits via an exception, `lock.lock()` shall be called prior to exiting the function scope.

   > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > -11- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > -12- Throws: Nothing `system_error` when an exception is required (30.2.2).

   > > -13- Error conditions:

   > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Predicate>
   > >     void wait(unique_lock<mutex>& lock, Predicate pred);

   > > > […]

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -16- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > > -17- Throws: `system_error` when an exception is required (30.2.2), timeout-related exceptions (30.2.4), or any exception thrown by `pred`.

   > > > -18- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Clock, class Duration>
   > >       cv_status wait_until(unique_lock<mutex>& lock,
   > >         const chrono::time_point<Clock, Duration>& abs_time);

   > > > […]

   > > > -20- Effects:

   > > > - […]

   > > > - If the function exits via an exception, `lock.lock()` shall be called prior to exiting the function scope.

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -21- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > > […]

   > > > -23- Throws: `system_error` when an exception is required (30.2.2) or timeout-related exceptions (30.2.4).

   > > > -24- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Rep, class Period>
   > >       cv_status wait_for(unique_lock<mutex>& lock,
   > >         const chrono::duration<Rep, Period>& rel_time);

   > > > […]

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -28- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > > […]

   > > > -29- Throws: `system_error` when an exception is required (30.2.2) or timeout-related exceptions (30.2.4).

   > > > -30- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Clock, class Duration, class Predicate>
   > >       bool wait_until(unique_lock<mutex>& lock,
   > >         const chrono::time_point<Clock, Duration>& abs_time,
   > >     	Predicate pred);

   > > > […]

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -33- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > > […]

   > > > -35- Throws: `system_error` when an exception is required (30.2.2), timeout-related exceptions (30.2.4), or any exception thrown by `pred`.

   > > > -36- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Rep, class Period, class Predicate>
   > >       bool wait_for(unique_lock<mutex>& lock,
   > >         const chrono::duration<Rep, Period>& rel_time,
   > >     	Predicate pred);

   > > > […]

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -40- Postcondition: `lock.owns_lock()` is true and `lock.mutex()` is locked by the calling thread.

   > > > […]

   > > > -42- Throws: `system_error` when an exception is required (30.2.2), timeout-related exceptions (30.2.4), or any exception thrown by `pred`.

   > > > -43- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

2. Edit 33.7.5 [thread.condition.condvarany] as indicated:

   >     template<class Lock>
   >     void wait(Lock& lock);

   > > […]

   > > -10- Effects:

   > > - Atomically calls `lock.unlock()` and blocks on `*this`.

   > > - When unblocked, calls `lock.lock()` (possibly blocking on the lock) and returns.

   > > - The function will unblock when signaled by a call to `notify_one()`, a call to `notify_all()`, or spuriously.

   > > - If the function exits via an exception, `lock.lock()` shall be called prior to exiting the function scope.

   > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > -11- Postcondition: `lock` is locked by the calling thread.

   > > -12- Throws: Nothing `system_error` when an exception is required (30.2.2).

   > > -13- Error conditions:

   > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Lock, class Clock, class Duration>
   > >       cv_status wait_until(Lock& lock, const chrono::time_point<Clock, Duration>& abs_time);

   > > > […]

   > > > -15- Effects:

   > > > - […]

   > > > - If the function exits via an exception, `lock.lock()` shall be called prior to exiting the function scope.

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -16- Postcondition: `lock` is locked by the calling thread.

   > > > […]

   > > > -18- Throws: `system_error` when an exception is required (30.2.2) or timeout-related exceptions (30.2.4).

   > > > -19- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.

   > >     template <class Lock, class Rep, class Period>
   > >       cv_status wait_for(Lock& lock, const chrono::duration<Rep, Period>& rel_time);

   > > > […]

   > > > -?- Remarks: If the function fails to meet the postcondition, `std::terminate()` shall be called (14.6.2 [except.terminate]). [Note: This can happen if the re-locking of the mutex throws an exception. — end note]

   > > > -22- Postcondition: `lock` is locked by the calling thread.

   > > > […]

   > > > -23- Throws: `system_error` when an exception is required (30.2.2) or timeout-related exceptions (30.2.4).

   > > > -24- Error conditions:

   > > > - equivalent error condition from `lock.lock()` or `lock.unlock()`.