RFR: 8291714: Implement a Multi-Reader Single-Writer mutex for Hotspot [v4]
Kim Barrett
kbarrett at openjdk.org
Thu Aug 25 21:46:03 UTC 2022
On Thu, 25 Aug 2022 13:06:01 GMT, Johan Sjölén <duke at openjdk.org> wrote:
>> May I please have a review for this PR which implements a `MRWMutex` class for Hotspot?
>>
>> This PR does 3 things:
>>
>> * Adds a port of ZGC's MRSW mutex (see [0]) to Hotspot
>> * Adds some new utilities for writing multi-threaded tests.
>> * Adds some tests for MRSW Mutex using these new utilities
>>
>> The ticket has some comments which might be worth checking out: https://bugs.openjdk.org/browse/JDK-8291714
>>
>> [0] Original source code here: https://github.com/openjdk/zgc/blob/zgc_generational/src/hotspot/share/gc/z/zJNICritical.cpp
>
> Johan Sjölén has updated the pull request with a new target base due to a merge or a rebase. The pull request now contains six commits:
>
> - Fix tests for updated threadHelper
> - Remove outdated threadHelper
> - Update documentation
> - Fix outdated headers and remove dead code
> - Review comments
> - Implement MRSWMutex
Changes requested by kbarrett (Reviewer).
src/hotspot/share/utilities/readWriteLock.cpp line 32:
> 30: #include "utilities/readWriteLock.hpp"
> 31:
> 32: void ReadWriteLock::read_lock(Thread *current) {
Consider asserting `current` really is the current thread, both here and in `write_lock`.
src/hotspot/share/utilities/readWriteLock.cpp line 32:
> 30: #include "utilities/readWriteLock.hpp"
> 31:
> 32: void ReadWriteLock::read_lock(Thread *current) {
The more common (though far from universal) style in HotSpot code cuddles the `*` punctuator with the type rather than the variable, i.e. `Thread* current`.
src/hotspot/share/utilities/readWriteLock.cpp line 38:
> 36: // Wait until unlocked by writer
> 37: auto await = [&]() {
> 38: Locker locker(&_mon);
In Mutex lock/unlock/wait/&etc there are various validity checks on the state of the current thread. This is going directly to a PlatformMonitor, so not doing any of those checks even though they seem like they are relevant for safe usage checking. This also doesn't support the InFlightMutexRelease stuff - not sure whether that matters here or not.
src/hotspot/share/utilities/readWriteLock.cpp line 42:
> 40: _mon.wait(0);
> 41: }
> 42: };
Why is this a lambda rather than a (private) ordinary member function? There's a bunch of these. Use of lambda reduces the separation of the code from it's use, but that doesn't seem to me worth the additional clutter. I'd rather a well-named helper function here, esp. since in this case would be the same helper as used by write_lock. Call it await_write_unlock. Consider putting the thread-type handling in the helper too, keeping this function small.
src/hotspot/share/utilities/readWriteLock.cpp line 64:
> 62: void ReadWriteLock::read_unlock() {
> 63: for (;;) {
> 64: const int32_t count = Atomic::load_acquire(&_count);
I think an invariant here is that count is never 0 or -1. Consider asserting that.
src/hotspot/share/utilities/readWriteLock.cpp line 67:
> 65:
> 66: if (count > 0) {
> 67: // No writer in progress, try to decrement reader count.
s/in progress/pending/
src/hotspot/share/utilities/readWriteLock.cpp line 72:
> 70: }
> 71: } else {
> 72: // Writer in progress, try to increment reader count.
s/in progress/pending/
src/hotspot/share/utilities/readWriteLock.cpp line 81:
> 79: if (count == -2) {
> 80: Locker locker(&_mon);
> 81: _mon.notify_all();
Unfortunately, this also wakes up all pending readers. I wonder if there's a way to avoid that by having two monitors (only having monitors and not bare condvars makes that harder).
src/hotspot/share/utilities/readWriteLock.cpp line 87:
> 85: return;
> 86: }
> 87: }
I found the control structure of read_unlock, with the use of continue and
eventual "fall through" to the exit, a bit confusing and requiring lining up
or counting braces. I think something like this (totally untested, but
intended to be equivalent except it should have comments) is easier to
follow:
void ReadWriteLock::read_unlock() {
for (;;) {
const int32_t count = Atomic::load_acquire(&_count);
if (count > 0) {
if (Atomic::cmpxchg(&_count, count, count - 1) == count) {
break;
}
} else if (Atomic::cmpxchg(&_count, count, count + 1) == count) {
if (count == -2) {
Locker locker(&_mon);
_mon.notify_all();
}
break;
}
}
}
I had a similar reaction to read_lock and write_lock.
src/hotspot/share/utilities/readWriteLock.cpp line 139:
> 137: return;
> 138: }
> 139: }
I was wondering why a lock for write doesn't hold the underlying
PlatformMonitor across the scope of the mutex region. Something like this
(completely untested)
void ReadWriteLock::write_lock(Thread* current) {
_mon.lock();
while (true) {
const int32_t count = Atomic::load_acquire(&_count);
if (count < 0) {
// Some other writer is waiting for readers to complete.
// Approx:
// while (Atomic::load_acquire(&_count)) _mon.wait();
await_write_unlock(current);
} else if (Atomic::cmpxchg(&_count, count, -(count + 1)) == count) {
// Claimed the write slot, but there might be active readers.
if (count != 0) {
// Approx:
// while (Atomic::load_acquire(&_count) != -1) _mon.wait();
await_no_active_readers(current);
}
return; // return still holding lock
} // else failed to claim write ownership, so try again.
}
}
void ReadWriteLock::write_unlock() {
assert(Atomic::load(&_count) == -1, "invariant");
Atomic::release_store(&_count, (int32_t)0);
_mon.notify_all();
_mon.unlock();
}
This seems simpler to me. Am I missing something?
And looking at this also makes me wonder if `_mon` can just be a normal
HotSpot `Monitor`, which would resolve many of the questions and comments I've
made that are mostly driven by the direct use of `PlatformMonitor`.
src/hotspot/share/utilities/readWriteLock.hpp line 82:
> 80: //
> 81: volatile int32_t _count;
> 82:
This class should be noncopyable. Maybe it is implicitly so via the `PlatformMonitor` member, but would be better to be explicit.
src/hotspot/share/utilities/readWriteLock.hpp line 89:
> 87: }
> 88:
> 89: ~ReadWriteLock() {
Use default destructor (either implicit or explicit) rather than empty body destructor.
src/hotspot/share/utilities/readWriteLock.hpp line 97:
> 95: void read_lock(Thread* current = Thread::current());
> 96: void read_unlock();
> 97: };
There should be RAII classes associated with locking for read and locking for write.
-------------
PR: https://git.openjdk.org/jdk/pull/9838
More information about the hotspot-runtime-dev
mailing list