Commit 1540c39c authored by Yi Zhang's avatar Yi Zhang Committed by Facebook GitHub Bot

Record owner gettid() if TrackThreadId is enabled

Summary:
Adds a new TrackThreadId template argument to SharedMutexImpl that when enabled, expands SharedMutex from 4 to 8 bytes (still with
4 byte alignment), and uses the extra space to record the thread ID
of a distinguished owning thread (upgrade or exclusive lock holder).
This dramatically simplifies debugging in some scenarios.  It adds enough
information that we could enforce that unlock happens on the same thread
as lock, but this diff doesn't actually add such a check.

A new TrackedSharedMutex class is added for SharedMutex with lock owner thread id tracking enabled. fb_localtime is the only user for now to reduce the risk.

Differential Revision: D24731513

fbshipit-source-id: 6b41fb05498842224feb088b1332794281b501a9
parent dd4c59a9
......@@ -32,6 +32,7 @@
#include <folly/portability/SysResource.h>
#include <folly/synchronization/AtomicRef.h>
#include <folly/synchronization/SanitizeThread.h>
#include <folly/system/ThreadId.h>
// SharedMutex is a reader-writer lock. It is small, very fast, scalable
// on multi-core, and suitable for use when readers or writers may block.
......@@ -274,6 +275,50 @@ FOLLY_EXPORT FOLLY_ALWAYS_INLINE uint32_t getMaxDeferredReaders() {
auto const value = cache.load(std::memory_order_acquire);
return FOLLY_LIKELY(!!value) ? value : getMaxDeferredReadersSlow(cache);
}
class NopOwnershipTracker {
public:
void beginThreadOwnership() {}
void maybeBeginThreadOwnership(bool) {}
void endThreadOwnership() {}
};
class ThreadIdOwnershipTracker {
public:
void beginThreadOwnership() {
assert(ownerTid_ == 0);
ownerTid_ = tid();
}
void maybeBeginThreadOwnership(bool own) {
if (own) {
beginThreadOwnership();
}
}
void endThreadOwnership() {
// if you want to check that unlock happens on the same thread as lock,
// assert that ownerTid_ == tid() here
ownerTid_ = 0;
}
private:
static unsigned tid() {
/* library-local */ static thread_local unsigned cached = 0;
auto z = cached;
if (z == 0) {
z = static_cast<unsigned>(getOSThreadID());
cached = z;
}
return z;
}
private:
// gettid() of thread holding the lock in U or E mode
unsigned ownerTid_ = 0;
};
} // namespace shared_mutex_detail
template <
......@@ -281,11 +326,21 @@ template <
typename Tag_ = void,
template <typename> class Atom = std::atomic,
bool BlockImmediately = false,
bool AnnotateForThreadSanitizer = kIsSanitizeThread && !ReaderPriority>
class SharedMutexImpl {
bool AnnotateForThreadSanitizer = kIsSanitizeThread && !ReaderPriority,
bool TrackThreadId = false>
class SharedMutexImpl : std::conditional_t<
TrackThreadId,
shared_mutex_detail::ThreadIdOwnershipTracker,
shared_mutex_detail::NopOwnershipTracker> {
private:
typedef std::conditional_t<
TrackThreadId,
shared_mutex_detail::ThreadIdOwnershipTracker,
shared_mutex_detail::NopOwnershipTracker>
OwnershipTrackerBase;
public:
static constexpr bool kReaderPriority = ReaderPriority;
typedef Tag_ Tag;
typedef SharedMutexToken Token;
......@@ -369,12 +424,14 @@ class SharedMutexImpl {
void lock() {
WaitForever ctx;
(void)lockExclusiveImpl(kHasSolo, ctx);
OwnershipTrackerBase::beginThreadOwnership();
annotateAcquired(annotate_rwlock_level::wrlock);
}
bool try_lock() {
WaitNever ctx;
auto result = lockExclusiveImpl(kHasSolo, ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::wrlock);
return result;
}
......@@ -383,6 +440,7 @@ class SharedMutexImpl {
bool try_lock_for(const std::chrono::duration<Rep, Period>& duration) {
WaitForDuration<Rep, Period> ctx(duration);
auto result = lockExclusiveImpl(kHasSolo, ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::wrlock);
return result;
}
......@@ -392,12 +450,14 @@ class SharedMutexImpl {
const std::chrono::time_point<Clock, Duration>& absDeadline) {
WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
auto result = lockExclusiveImpl(kHasSolo, ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::wrlock);
return result;
}
void unlock() {
annotateReleased(annotate_rwlock_level::wrlock);
OwnershipTrackerBase::endThreadOwnership();
// It is possible that we have a left-over kWaitingNotS if the last
// unlock_shared() that let our matching lock() complete finished
// releasing before lock()'s futexWait went to sleep. Clean it up now
......@@ -507,6 +567,7 @@ class SharedMutexImpl {
}
void unlock_and_lock_shared() {
OwnershipTrackerBase::endThreadOwnership();
annotateReleased(annotate_rwlock_level::wrlock);
annotateAcquired(annotate_rwlock_level::rdlock);
// We can't use state_ -=, because we need to clear 2 bits (1 of which
......@@ -534,6 +595,7 @@ class SharedMutexImpl {
void lock_upgrade() {
WaitForever ctx;
(void)lockUpgradeImpl(ctx);
OwnershipTrackerBase::beginThreadOwnership();
// For TSAN: treat upgrade locks as equivalent to read locks
annotateAcquired(annotate_rwlock_level::rdlock);
}
......@@ -541,6 +603,7 @@ class SharedMutexImpl {
bool try_lock_upgrade() {
WaitNever ctx;
auto result = lockUpgradeImpl(ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::rdlock);
return result;
}
......@@ -550,6 +613,7 @@ class SharedMutexImpl {
const std::chrono::duration<Rep, Period>& duration) {
WaitForDuration<Rep, Period> ctx(duration);
auto result = lockUpgradeImpl(ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::rdlock);
return result;
}
......@@ -559,12 +623,14 @@ class SharedMutexImpl {
const std::chrono::time_point<Clock, Duration>& absDeadline) {
WaitUntilDeadline<Clock, Duration> ctx{absDeadline};
auto result = lockUpgradeImpl(ctx);
OwnershipTrackerBase::maybeBeginThreadOwnership(result);
annotateTryAcquired(result, annotate_rwlock_level::rdlock);
return result;
}
void unlock_upgrade() {
annotateReleased(annotate_rwlock_level::rdlock);
OwnershipTrackerBase::endThreadOwnership();
auto state = (state_ -= kHasU);
assert((state & (kWaitingNotS | kHasSolo)) == 0);
wakeRegisteredWaiters(state, kWaitingE | kWaitingU);
......@@ -581,6 +647,7 @@ class SharedMutexImpl {
void unlock_upgrade_and_lock_shared() {
// No need to annotate for TSAN here because we model upgrade and shared
// locks as the same.
OwnershipTrackerBase::endThreadOwnership();
auto state = (state_ -= kHasU - kIncrHasS);
assert((state & (kWaitingNotS | kHasSolo)) == 0);
wakeRegisteredWaiters(state, kWaitingE | kWaitingU);
......@@ -1522,6 +1589,8 @@ class SharedMutexImpl {
typedef SharedMutexImpl<true> SharedMutexReadPriority;
typedef SharedMutexImpl<false> SharedMutexWritePriority;
typedef SharedMutexWritePriority SharedMutex;
typedef SharedMutexImpl<false, void, std::atomic, false, false, true>
SharedMutexTracked;
typedef SharedMutexImpl<false, void, std::atomic, false, false>
SharedMutexSuppressTSAN;
......@@ -1535,19 +1604,22 @@ template <
typename Tag_,
template <typename> class Atom,
bool BlockImmediately,
bool AnnotateForThreadSanitizer>
bool AnnotateForThreadSanitizer,
bool TrackThreadId>
alignas(hardware_destructive_interference_size) typename SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::DeferredReaderSlot
AnnotateForThreadSanitizer,
TrackThreadId>::DeferredReaderSlot
SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::deferredReaders
AnnotateForThreadSanitizer,
TrackThreadId>::deferredReaders
[shared_mutex_detail::kMaxDeferredReadersAllocated *
kDeferredSeparationFactor] = {};
......@@ -1556,39 +1628,45 @@ template <
typename Tag_,
template <typename> class Atom,
bool BlockImmediately,
bool AnnotateForThreadSanitizer>
bool AnnotateForThreadSanitizer,
bool TrackThreadId>
FOLLY_SHAREDMUTEX_TLS uint32_t SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::tls_lastTokenlessSlot = 0;
AnnotateForThreadSanitizer,
TrackThreadId>::tls_lastTokenlessSlot = 0;
template <
bool ReaderPriority,
typename Tag_,
template <typename> class Atom,
bool BlockImmediately,
bool AnnotateForThreadSanitizer>
bool AnnotateForThreadSanitizer,
bool TrackThreadId>
FOLLY_SHAREDMUTEX_TLS uint32_t SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::tls_lastDeferredReaderSlot = 0;
AnnotateForThreadSanitizer,
TrackThreadId>::tls_lastDeferredReaderSlot = 0;
template <
bool ReaderPriority,
typename Tag_,
template <typename> class Atom,
bool BlockImmediately,
bool AnnotateForThreadSanitizer>
bool AnnotateForThreadSanitizer,
bool TrackThreadId>
bool SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::tryUnlockTokenlessSharedDeferred() {
AnnotateForThreadSanitizer,
TrackThreadId>::tryUnlockTokenlessSharedDeferred() {
auto bestSlot =
make_atomic_ref(tls_lastTokenlessSlot).load(std::memory_order_relaxed);
// use do ... while to avoid calling
......@@ -1613,14 +1691,16 @@ template <
typename Tag_,
template <typename> class Atom,
bool BlockImmediately,
bool AnnotateForThreadSanitizer>
bool AnnotateForThreadSanitizer,
bool TrackThreadId>
template <class WaitContext>
bool SharedMutexImpl<
ReaderPriority,
Tag_,
Atom,
BlockImmediately,
AnnotateForThreadSanitizer>::
AnnotateForThreadSanitizer,
TrackThreadId>::
lockSharedImpl(uint32_t& state, Token* token, WaitContext& ctx) {
const uint32_t maxDeferredReaders =
shared_mutex_detail::getMaxDeferredReaders();
......
......@@ -88,6 +88,7 @@ TEST(SharedMutex, basic) {
runBasicTest<SharedMutexReadPriority>();
runBasicTest<SharedMutexWritePriority>();
runBasicTest<SharedMutexSuppressTSAN>();
runBasicTest<SharedMutexTracked>();
}
template <typename Lock>
......@@ -169,6 +170,7 @@ TEST(SharedMutex, basic_holders) {
runBasicHoldersTest<SharedMutexReadPriority>();
runBasicHoldersTest<SharedMutexWritePriority>();
runBasicHoldersTest<SharedMutexSuppressTSAN>();
runBasicHoldersTest<SharedMutexTracked>();
}
template <typename Lock>
......@@ -196,6 +198,7 @@ TEST(SharedMutex, many_read_locks_with_tokens) {
runManyReadLocksTestWithTokens<SharedMutexReadPriority>();
runManyReadLocksTestWithTokens<SharedMutexWritePriority>();
runManyReadLocksTestWithTokens<SharedMutexSuppressTSAN>();
runManyReadLocksTestWithTokens<SharedMutexTracked>();
}
template <typename Lock>
......@@ -221,6 +224,7 @@ TEST(SharedMutex, many_read_locks_without_tokens) {
runManyReadLocksTestWithoutTokens<SharedMutexReadPriority>();
runManyReadLocksTestWithoutTokens<SharedMutexWritePriority>();
runManyReadLocksTestWithoutTokens<SharedMutexSuppressTSAN>();
runManyReadLocksTestWithoutTokens<SharedMutexTracked>();
}
template <typename Lock>
......@@ -251,6 +255,7 @@ TEST(SharedMutex, timeout_in_past) {
runTimeoutInPastTest<SharedMutexReadPriority>();
runTimeoutInPastTest<SharedMutexWritePriority>();
runTimeoutInPastTest<SharedMutexSuppressTSAN>();
runTimeoutInPastTest<SharedMutexTracked>();
}
template <class Func>
......@@ -338,6 +343,7 @@ TEST(SharedMutex, failing_try_timeout) {
runFailingTryTimeoutTest<SharedMutexReadPriority>();
runFailingTryTimeoutTest<SharedMutexWritePriority>();
runFailingTryTimeoutTest<SharedMutexSuppressTSAN>();
runFailingTryTimeoutTest<SharedMutexTracked>();
}
template <typename Lock>
......@@ -382,6 +388,7 @@ TEST(SharedMutex, basic_upgrade_tests) {
runBasicUpgradeTest<SharedMutexReadPriority>();
runBasicUpgradeTest<SharedMutexWritePriority>();
runBasicUpgradeTest<SharedMutexSuppressTSAN>();
runBasicUpgradeTest<SharedMutexTracked>();
}
TEST(SharedMutex, read_has_prio) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment