Document Number: N2026
Submitters: Martin Sebor, Torvald Riegel
Submission Date: March 23, 2016
Subject: Recursive Mutex Unspecified

Summary

In §7.26.1 Introduction the C11 threads library defines the mtx_recursive enumerator...

which is passed to mtx_init to create a mutex object that supports recursive locking;

In §7.26.4.2 The mtx_init function the standard then specifies that the function creates four kinds of mutex objects:

However, the semantics of neither recursive locking nor recursive mutex objects are specified. In particular, the only other normative reference to a recursive mutex is in §7.26.4.3 The mtx_lock function when describing its effects on non-recursive mutexes:

The mtx_lock function blocks until it locks the mutex pointed to by mtx. If the mutex is non-recursive, it shall not be locked by the calling thread. Prior calls to mtx_unlock on the same mutex shall synchronize with this operation.

Thus, the effects of calling mtx_lock() on mutex objects of the three recursive kinds are unspecified and the only kinds of mutexes whose semantics are specified are mtx_plain and mtx_timed.

The underspecification of recursive mutexes has come up before, for example during the committee's discussion of DR 469 but the authors feel the issue is important enough to warrant its own defect report.

Suggested Technical Corrigendum

The authors see two options for handling this defect: Either define the semantics of the C11 threads library functions for recursive mutex objects or remove recursive mutexes from the specification.

This suggested technical corrigendum outlines the first option.

Add a new subsection to §7.26.1 Introduction titled (for example) §7.26.1.1 Recursive Mutex, with the following text (adopted from the POSIX specification):

Each recursive mutex object shall maintain the concept of a lock count. The lock count of an available mutex shall be zero. When a thread successfully acquires ownership of a recursive mutex for the first time by calling the mtx_lock(), mtx_timedlock(), or mtx_trylock() function, the lock count shall be set to one. Every subsequent time the owning thread relocks the same mutex by calling one of these functions, its lock count shall be incremented by one. Each time the owning thread unlocks the mutex by calling the mtx_unlock() function, its lock count shall be decremented by one. When the lock count reaches zero, the mutex owner shall be cleared and the object made available for other threads to acquire.

Modify §7.26.4.3 The mtx_lock function, paragraph 2 as follows:

The mtx_lock function shall acquireblocks until it locks the mutex pointed to by mtx. If the mutex is already owned by another thread, the calling thread shall block until the mutex becomes available. Otherwise, if the mutex is recursive, then its lock count shall be incremented. If the mutex is non-recursive, it shall not be already owned locked by the calling thread. Prior calls to mtx_unlock on the same mutex shall synchronize with this operation.

Modify §7.26.4.4 The mtx_timedlock function, paragraph 2 as follows:

The mtx_timedlock function shall acquire endeavors to block until it locks the mutex pointed to by mtx. If the mutex is already owned by another thread, the calling thread shall block until either the mutex becomes available or until after the TIME_UTC-based calendar time pointed to by ts, whichever comes first. Otherwise, if the mutex is recursive then its lock count shall be incremented. If the mutex is non-recursive, it shall not be already owned by the calling thread. The specified mutex shall support timeout. If the operation succeeds, prior calls to mtx_unlock on the same mutex shall synchronize with this operation.

Modify §7.26.4.5 The mtx_trylock function, paragraph 2 as follows:

The mtx_trylock function shall acquire endeavors to lock the mutex pointed to by mtx. If the mutex is recursive and owned by the calling thread then its lock count shall be incremented and the function shall return successfully and without blocking. Otherwise, Iif the mutex is already owned by another thread locked, the function shall returnsthrd_busy without blocking to indicate failure. If the mutex is non-recursive, it shall not be already owned by the calling thread. If the operation succeeds, prior calls to mtx_unlock on the same mutex shall synchronize with this operation.

Note that in addition to clarifying the effects on recursive mutexes the change above also relaxes the requirement on implementations to accept calls to the mtx_trylock function made by a thread that already owns the mutex. This change is proposed in order to allow more efficient implementations that rely on hardware support for locking and lock elision in hardware implementations that don't have the ability to track this case (an example is Hardware Lock Elision (HLE) support in Intel Transactional Sychnronization Extensions (TSX)). Although the existing guarantee is also provided by POSIX, a precedent for the relaxed requirement is the C++ threads specification.

Modify §7.26.4.6 The mtx_unlock function, paragraph 2 as follows:

The mtx_unlock function unlocks the mutex pointed to by mtx. The mutex pointed to by mtx shall be owned locked by the calling thread. If the mutex is recursive then its lock count shall be decremented. When the lock count reaches zero, or if the mutex is non-recursive, the mutex owner shall be cleared and the mutex released. The released mutex becomes available to other threads.