This is an archive of the discontinued LLVM Phabricator instance.

sanitizer_common: add new mutex
ClosedPublic

Authored by dvyukov on Jul 18 2021, 2:15 AM.

Details

Summary

We currently have 3 different mutexes:

  • RWMutex
  • BlockingMutex
  • __tsan::Mutex

RWMutex and __tsan::Mutex are roughly the same,
except that tsan version supports deadlock detection.
BlockingMutex degrades better under heavy contention
from lots of threads (blocks in OS), but much slower
for light contention and has non-portable performance
and has larger static size and is not reader-writer.

Add a new mutex that combines all advantages of these
mutexes: it's reader-writer, has fast non-contended path,
supports blocking to gracefully degrade under higher contention,
has portable size/performance.

For now it's named Mutex2 for incremental submission. The plan is to:

  • land this change
  • then move deadlock detection logic from tsan
  • then rename it to Mutex and remove tsan Mutex
  • then typedef RWMutex/BlockingMutex to this mutex

SpinMutex stays as separate type because it has faster fast path:
1 atomic RMW per lock/unlock as compared to 2 for this mutex.

Diff Detail

Event Timeline

dvyukov created this revision.Jul 18 2021, 2:15 AM
dvyukov requested review of this revision.Jul 18 2021, 2:15 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 18 2021, 2:15 AM
Herald added a subscriber: Restricted Project. · View Herald Transcript
melver added inline comments.Jul 19 2021, 4:56 AM
compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
91

Can we make it have a constexpr constructor?
Probably also needs initializing state_ inline.

106

s/We've spin/We've spun/

157

Would this be cleaner if it was:

do {
....
} while (UNLIKELY(!atomic_compare_exchange_weak(...)));
if (UNLIKELY(wake_writer))
  writers_.Post();
else if (UNLIKELY(wake_readers))
  readers_.Post(wake_readers);

And similar for the other loops in Read*() that do an explicit 'return;' at the end.

That's my preference, because it makes it visually more obvious what is actually repeatedly executed. But of course either is fine if you don't like it.

196

Should there be a check (new_state & kReaderLockMask) == 0 to decide if to wake a waiting writer?

Or is there a performance benefit to waking the writer early, in case there are more readers still locked, and that writer then spinning and hopefully not going back to sleep and more quickly seeing the last reader unlocking?

217

Should this only check & (kReaderLockMask | kWriterLock) ?

I think for the purpose of testing, this would be more accurate and potentially catch those bits not set even though we've entered a C-S.

For the tests, I'd even go so far and add a separate CheckWriterLocked() and CheckReaderLocked() in addition to this.

235

"woken" is used in present perfect tense ("writer has woken up" -> woke in the past, and still awake) or could be passive ("was woken up by other thread").

Does this bit indicate the writer is "awake and spinning", or rather that it "was woken up by somebody else"?

If I read it right, I think it means the former. If that's the case, perhaps "a writer is awake and spin-waiting" is clearer (with flag "kWriterSpinWait" or similar)?

238

s/worken/woken/
?

compiler-rt/lib/sanitizer_common/tests/sanitizer_mutex_test.cpp
38

Can this keep calling mtx_.CheckLocked() (CheckWriterLocked()) ?

58

Call mtx_.CheckLocked() ? Or perhaps just mtx_.CheckReaderLocked() if it can be added.

dvyukov updated this revision to Diff 359774.Jul 19 2021, 6:41 AM
dvyukov marked 7 inline comments as done.

addressed comments

PTAL

compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
91

I did not make it constexpr b/c then somebody will use it as a global object, but then we will also need to remove DCHECK in destructor. I don't know what's better.

196

Ha, good point! I intended to check (new_state & kReaderLockMask) == 0.
The point re unblocking a writer earlier is also good. I don't know what's better. Since I intended to check kReaderLockMask and you asked about it, let's go with that additional check. It looks more straightforward.

melver accepted this revision.Jul 19 2021, 8:08 AM
melver added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
91

It's still usable as a global object, compiler won't complain, unless that global has attribute [[clang::require_constant_initialization]]. Without it, the only way to discourage that is to make the constructor private and instead have a factory function, but I think that's not something we should do for a Mutex.

If used as a global object, the DCHECK may then crash in other spectacular ways, but at least it'd crash/warn somehow if state_!=0 (and there's a bug :-)). In production builds the DCHECK doesn't exist.

Perhaps a comment, if it really shouldn't be used as a global object, but I think as-is (modulo DCHECK), there is no technical limitation that makes it unusable as a global object.

So I think it's still safer + more efficient to make it constexpr.

133

s/spinned/spun/

This revision is now accepted and ready to land.Jul 19 2021, 8:08 AM
vitalybuka accepted this revision.Jul 19 2021, 11:25 AM
vitalybuka added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
157

I slightly prefer previous version. Uninitialized variable now concern me more than continue and return from loop. It requires more mental efforts to track values. Also prev version was similar to Lock() implementation.

dvyukov updated this revision to Diff 360019.Jul 19 2021, 10:56 PM
dvyukov marked 2 inline comments as done.

made ctor constexpr

compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
157

I can change it again, but, folks, you need to decide :)
Marco?

melver accepted this revision.Jul 19 2021, 11:08 PM
melver added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
157

I think it's on you now, whatever you prefer.

( Personally, I'm not worried about declaring variables and a few lines below clearly initialize them, perhaps because I've been staring at too much Linux kernel code and this style is all over the place ... so I'd do that and get the nicer-to-follow control-flow. But there are good arguments the other way around as well. But in the end this is all just bikeshedding. :-) )

This revision was landed with ongoing or failed builds.Jul 19 2021, 11:20 PM
This revision was automatically updated to reflect the committed changes.
dvyukov added inline comments.Jul 19 2021, 11:21 PM
compiler-rt/lib/sanitizer_common/sanitizer_mutex.h
157

OK, I will go with the current version then just because that's the code I have now (and I am a bit afraid of introducing bugs while changing this code).