986. Generic try_lock contradiction

Section: 33.4.5 [thread.lock.algorithm] Status: C++11 Submitter: Chris Fairles Opened: 2009-02-14 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [thread.lock.algorithm].

View all issues with C++11 status.

Discussion:

In 33.4.5 [thread.lock.algorithm], the generic try_lock effects (p2) say that a failed try_lock is when it either returns false or throws an exception. In the event a call to try_lock does fail, by either returning false or throwing an exception, it states that unlock shall be called for all prior arguments. Then the returns clause (p3) goes on to state in a note that after returning, either all locks are locked or none will be. So what happens if multiple locks fail on try_lock?

Example:

#include <mutex>

int main() {
 std::mutex m0, m1, m2;
 std::unique_lock<std::mutex> l0(m0, std::defer_lock);
 std::unique_lock<std::mutex> l1(m1); //throws on try_lock
 std::unique_lock<std::mutex> l2(m2); //throws on try_lock

 int result = std::try_lock(l0, l1, l2);

 assert( !l0.owns_lock() );
 assert( l1.owns_lock() ); //??
 assert( l2.owns_lock() ); //??
}

The first lock's try_lock succeeded but, being a prior argument to a lock whose try_lock failed, it gets unlocked as per the effects clause of 33.4.5 [thread.lock.algorithm]. However, 2 locks remain locked in this case but the return clause states that either all arguments shall be locked or none will be. This seems to be a contradiction unless the intent is for implementations to make an effort to unlock not only prior arguments, but the one that failed and those that come after as well. Shouldn't the note only apply to the arguments that were successfully locked?

Further discussion and possible resolutions in c++std-lib-23049.

[ Summit: ]

Move to review. Agree with proposed resolution.

[ Batavia (2009-05): ]

We agree with the proposed resolution. Move to Tentatively Ready.

Proposed resolution:

Change 33.4.5 [thread.lock.algorithm], p2:

-2- Effects: Calls try_lock() for each argument in order beginning with the first until all arguments have been processed or a call to try_lock() fails, either by returning false or by throwing an exception. If a call to try_lock() fails, unlock() shall be called for all prior arguments and there shall be no further calls to try_lock().

Delete the note from 33.4.5 [thread.lock.algorithm], p3

-3- Returns: -1 if all calls to try_lock() returned true, otherwise a 0-based index value that indicates the argument for which try_lock() returned false. [Note: On return, either all arguments will be locked or none will be locked. -- end note]