2093. Throws clause of condition_variable::wait with predicate

Section: 32.7.4 [thread.condition.condvar] Status: C++14 Submitter: Alberto Ganesh Barbati Opened: 2011-10-27 Last modified: 2015-10-20

Priority: Not Prioritized

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

View all issues with C++14 status.

Discussion:

the Throws: clause of condition_variable::wait/wait_xxx functions that take a predicate argument is:

Throws: system_error when an exception is required (32.2.2 [thread.req.exception]).

If executing the predicate throws an exception, I would expect such exception to propagate unchanged to the caller, but the throws clause seems to indicate that it gets mutated into a system_error. That's because of 16.3.2.4 [structure.specifications]/4:

"If F's semantics contains a Throws:, Postconditions:, or Complexity: element, then that supersedes any occurrences of that element in the code sequence."

Is my interpretation correct? Does it match the intent?

Daniel comments:

I don't think that this interpretation is entirely correct, the wording does not say that std::system_error or a derived class must be thrown, it simply is underspecified in this regard (The extreme interpretation is that the behaviour would be undefined, but that would be too far reaching I think). We have better wording for this in 32.6.7.2 [thread.once.callonce] p4, where it says:

"Throws: system_error when an exception is required (32.2.2 [thread.req.exception]), or any exception thrown by func."

or in 32.4.5 [thread.thread.this] p6/p9:

"Throws: Nothing if Clock satisfies the TrivialClock requirements (30.3 [time.clock.req]) and operations of Duration do not throw exceptions. [ Note: instantiations of time point types and clocks supplied by the implementation as specified in 30.7 [time.clock] do not throw exceptions. — end note ]"

So, the here discussed Throws elements should add lines along the lines of

"Any exception thrown by operations of pred."

and similar wording for time-related operations:

"Any exception thrown by operations of Duration",

"Any exception thrown by operations of chrono::duration<Rep, Period>"

[2011-11-28: Ganesh comments and suggests wording]

As for the discussion about the exception thrown by the manipulation of time-related objects, I believe the argument applies to all functions declared in 32 [thread]. Therefore, instead of adding wording to each member, I would simply move those requirements from 32.4.5 [thread.thread.this] p6/p9 to a new paragraph in 32.2.4 [thread.req.timing].

As for 32.7.5 [thread.condition.condvarany], the member functions wait() and wait_until() are described only in terms of the Effects: clause (so strictly speaking, they need no changes), however, wait_for() is described with a full set of clauses including Throws: and Error conditions:. Either we should add those clauses to wait/wait_until with changes similar to the one above, or remove paragraphs 29 to 34 entirely. By the way, even paragraph 26 could be removed IMHO.

[2012, Kona]

We like the idea behind the proposed resolution.

Modify the first addition to read instead: "Functions that specify a timeout, will throw if an operation on a clock, time point, or time duration throws an exception."

In the note near the bottom change "even if the timeout has already expired" to "or if the timeout has already expired". (This is independent, but the original doesn't seem to make sense.)

Move to Review.

[2012, Portland]

Concurrency move to Ready with slightly ammended wording.

[2013-04-20 Bristol]

Proposed resolution:

This wording is relative to N3337.

  1. Add a new paragraph at the end of 32.2.4 [thread.req.timing]:

    […]

    -6- The resolution of timing provided by an implementation depends on both operating system and hardware. The finest resolution provided by an implementation is called the native resolution.

    -7- Implementation-provided clocks that are used for these functions shall meet the TrivialClock requirements (30.3 [time.clock.req]).

    -?- Functions that specify a timeout, will throw if, during the execution of this function, a clock, time point, or time duration throws an exception. [ Note: instantiations of clock, time point and duration types supplied by the implementation as specified in 30.7 [time.clock] do not throw exceptions. — end note]

  2. Change 32.4.5 [thread.thread.this] as indicated:

    template <class Clock, class Duration>
      void sleep_until(const chrono::time_point<Clock, Duration>& abs_time);;
    

    -4- Effects: Blocks the calling thread for the absolute timeout (32.2.4 [thread.req.timing]) specified by abs_time.

    -5- Synchronization: None.

    -6- Throws: timeout-related exceptions (32.2.4 [thread.req.timing]).Nothing if Clock satisfies the TrivialClock requirements (30.3 [time.clock.req]) and operations of Duration do not throw exceptions. [ Note: instantiations of time point types and clocks supplied by the implementation as specified in 30.7 [time.clock] do not throw exceptions. — end note]

    template <class Rep, class Period>
      void sleep_for(const chrono::duration<Rep, Period>& rel_time);;
    

    -7- Effects: Blocks the calling thread for the relative timeout (32.2.4 [thread.req.timing]) specified by rel_time.

    -8- Synchronization: None.

    -9- Throws: timeout-related exceptions (32.2.4 [thread.req.timing]).Nothing if operations of chrono::duration<Rep, Period> do not throw exceptions. [ Note: instantiations of time point types and clocks supplied by the implementation as specified in 30.7 [time.clock] do not throw exceptions. — end note]

  3. Change 32.6.4.3 [thread.timedmutex.requirements] as indicated:

    -3- The expression m.try_lock_for(rel_time) shall be well-formed and have the following semantics:

    […]

    -5- Effects: The function attempts to obtain ownership of the mutex within the relative timeout (32.2.4 [thread.req.timing]) specified by rel_time. If the time specified by rel_time is less than or equal to rel_time.zero(), the function attempts to obtain ownership without blocking (as if by calling try_lock()). The function shall return within the timeout specified by rel_time only if it has obtained ownership of the mutex object. [Note: As with try_lock(), there is no guarantee that ownership will be obtained if the lock is available, but implementations are expected to make a strong effort to do so. — end note]

    […]

    -8- Synchronization: If try_lock_for() returns true, prior unlock() operations on the same object synchronize with (6.9.2 [intro.multithread]) this operation.

    -9- Throws: timeout-related exceptions (32.2.4 [thread.req.timing]).Nothing.

    -10- The expression m.try_lock_until(abs_time) shall be well-formed and have the following semantics:

    […]

    -12- Effects: The function attempts to obtain ownership of the mutex. If abs_time has already passed, the function attempts to obtain ownership without blocking (as if by calling try_lock()). The function shall return before the absolute timeout (32.2.4 [thread.req.timing]) specified by abs_time only if it has obtained ownership of the mutex object. [Note: As with try_lock(), there is no guarantee that ownership will be obtained if the lock is available, but implementations are expected to make a strong effort to do so. — end note]

    […]

    -15- Synchronization: If try_lock_until() returns true, prior unlock() operations on the same object synchronize with (6.9.2 [intro.multithread]) this operation.

    -16- Throws: timeout-related exceptions (32.2.4 [thread.req.timing]).Nothing.

  4. Change 32.7.4 [thread.condition.condvar] as indicated:

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

    […]

    -15- Effects: Equivalent to:

    while (!pred())
      wait(lock);
    

    […]

    -17- Throws: std::system_error when an exception is required (32.2.2 [thread.req.exception]), timeout-related exceptions (32.2.4 [thread.req.timing]), or any exception thrown by pred.

    […]

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

    […]

    -23- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]) or timeout-related exceptions (32.2.4 [thread.req.timing]).

    […]

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

    […]

    -26- Effects: as ifEquivalent to:

    return wait_until(lock, chrono::steady_clock::now() + rel_time);
    

    […]

    -29- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]) or timeout-related exceptions (32.2.4 [thread.req.timing]).

    […]

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

    […]

    -32- Effects: Equivalent to:

    while (!pred())
      if (wait_until(lock, abs_time) == cv_status::timeout)
        return pred();
    return true;
    

    -33- Returns: pred()

    […]

    -36- Throws: std::system_error when an exception is required (32.2.2 [thread.req.exception]), timeout-related exceptions (32.2.4 [thread.req.timing]), or any exception thrown by pred.

    […]

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

    […]

    -39- Effects: as ifEquivalent to:

    return wait_until(lock, chrono::steady_clock::now() + rel_time, std::move(pred));
    

    […]

    -42- Returns: pred()

    […]

    -44- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]), timeout-related exceptions (32.2.4 [thread.req.timing]), or any exception thrown by pred.

    […]

  5. Change 32.7.5 [thread.condition.condvarany] as indicated:

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

    -14- Effects: Equivalent to:

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

    […]

    -18- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]) or any timeout-related exceptions (32.2.4 [thread.req.timing]).

    […]

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

    […]

    -20- Effects: as ifEquivalent to:

    return wait_until(lock, chrono::steady_clock::now() + rel_time);
    

    […]

    -23- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]) or any timeout-related exceptions (32.2.4 [thread.req.timing]).

    […]

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

    -25- Effects: Equivalent to:

    while (!pred())
      if (wait_until(lock, abs_time) == cv_status::timeout)
        return pred();
    return true;
    

    -26- Returns: pred()[Note: There is no blocking if pred() is initially true, or if the timeout has already expired. — end note]

    -27- [Note: The returned value indicates whether the predicate evaluates to true regardless of whether the timeout was triggered. end note]

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

    -28- Effects: as ifEquivalent to:

    return wait_until(lock, chrono::steady_clock::now() + rel_time, std::move(pred));
    

    -29- [Note: There is no blocking if pred() is initially true, even if the timeout has already expired. — end note] -30- Postcondition: lock is locked by the calling thread.

    -31- Returns: pred()

    -32- [Note: The returned value indicates whether the predicate evaluates to true regardless of whether the timeout was triggered. — end note]

    -33- Throws: system_error when an exception is required (32.2.2 [thread.req.exception]).

    -34- Error conditions:

    • equivalent error condition from lock.lock() or lock.unlock().