Description
Mutex
primitive provides the concept of ownership -- an additional token passed to every operation to ensure that the lock is not being operated with the same owner concurrently, catches (not supported) reentrancy and prevents logical races that are otherwise hard to debug.
Notably, every locking operation has the following note:
When
owner
is specified (non-null value) and this mutex is already locked with the same token (same identity), this function throws [IllegalStateException].
This contract makes sense in theory -- e.g. it deterministically prevents reentrancy issues and some trivial races, thus proving itself quite useful.
But it is much more complex in practice, for example, consider the following scenario:
mutex.lock(o1)
launch { mutex.lock(o2) }
launch { mutex.lock(o2) }
// ... sometimes later ...
mutex.unlock(o1)
This code works, though, depending on the interpretation, it should or should not throw IllegalStateException
.
Things get more complicated when lock
implementation is taken into account (tryLock
+ slow path) as well as when actual concurrency is present: detecting such scenarios in a robust and linearizable manner doesn't seem to be practical and requires O(n)
operations (where n is a number of waiters).
We have to make a decision about the reasonable implementation complexity and thoroughly document the contract, even if we are going to leave it as is