Commit 9feea214 authored by Rui Zhang's avatar Rui Zhang Committed by Facebook Github Bot

Add eligible-for-elision accessors to folly::SharedMutex.

Summary: Lock elision requires non-mutating functions that check if lock acquisition could succeed. This diff adds such functions to folly::SharedMutex, including eligible_for_lock_elision(), eligible_for_lock_upgrade_elision(), and eligible_for_lock_shared_elision(). The diff also adds assertions to validate these functions' correctness under single-threaded executions.

Reviewed By: nbronson

Differential Revision: D15545398

fbshipit-source-id: 0037d473c9dd360f7143ea4c4c9092fb9bb7f5f9
parent 1ebd3b04
...@@ -324,6 +324,34 @@ class SharedMutexImpl { ...@@ -324,6 +324,34 @@ class SharedMutexImpl {
annotateDestroy(); annotateDestroy();
} }
// Checks if an exclusive lock could succeed so that lock elision could be
// enabled. Different from the two eligible_for_lock_{upgrade|shared}_elision
// functions, this is a conservative check since kMayDefer indicates
// "may-existing" deferred readers.
bool eligible_for_lock_elision() const {
// We rely on the transaction for linearization. Wait bits are
// irrelevant because a successful transaction will be in and out
// without affecting the wakeup. kBegunE is also okay for a similar
// reason.
auto state = state_.load(std::memory_order_relaxed);
return (state & (kHasS | kMayDefer | kHasE | kHasU)) == 0;
}
// Checks if an upgrade lock could succeed so that lock elision could be
// enabled.
bool eligible_for_lock_upgrade_elision() const {
auto state = state_.load(std::memory_order_relaxed);
return (state & (kHasE | kHasU)) == 0;
}
// Checks if a shared lock could succeed so that lock elision could be
// enabled.
bool eligible_for_lock_shared_elision() const {
// No need to honor kBegunE because a transaction doesn't block anybody
auto state = state_.load(std::memory_order_relaxed);
return (state & kHasE) == 0;
}
void lock() { void lock() {
WaitForever ctx; WaitForever ctx;
(void)lockExclusiveImpl(kHasSolo, ctx); (void)lockExclusiveImpl(kHasSolo, ctx);
......
...@@ -48,13 +48,19 @@ void runBasicTest() { ...@@ -48,13 +48,19 @@ void runBasicTest() {
SharedMutexToken token2; SharedMutexToken token2;
SharedMutexToken token3; SharedMutexToken token3;
EXPECT_TRUE(lock.eligible_for_lock_elision());
EXPECT_TRUE(lock.try_lock()); EXPECT_TRUE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token1)); EXPECT_FALSE(lock.try_lock_shared(token1));
lock.unlock(); lock.unlock();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1)); EXPECT_TRUE(lock.try_lock_shared(token1));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token2)); EXPECT_TRUE(lock.try_lock_shared(token2));
lock.lock_shared(token3); lock.lock_shared(token3);
lock.unlock_shared(token3); lock.unlock_shared(token3);
...@@ -90,44 +96,57 @@ void runBasicHoldersTest() { ...@@ -90,44 +96,57 @@ void runBasicHoldersTest() {
{ {
// create an exclusive write lock via holder // create an exclusive write lock via holder
typename Lock::WriteHolder holder(lock); typename Lock::WriteHolder holder(lock);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token)); EXPECT_FALSE(lock.try_lock_shared(token));
// move ownership to another write holder via move constructor // move ownership to another write holder via move constructor
typename Lock::WriteHolder holder2(std::move(holder)); typename Lock::WriteHolder holder2(std::move(holder));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token)); EXPECT_FALSE(lock.try_lock_shared(token));
// move ownership to another write holder via assign operator // move ownership to another write holder via assign operator
typename Lock::WriteHolder holder3(nullptr); typename Lock::WriteHolder holder3(nullptr);
holder3 = std::move(holder2); holder3 = std::move(holder2);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token)); EXPECT_FALSE(lock.try_lock_shared(token));
// downgrade from exclusive to upgrade lock via move constructor // downgrade from exclusive to upgrade lock via move constructor
typename Lock::UpgradeHolder holder4(std::move(holder3)); typename Lock::UpgradeHolder holder4(std::move(holder3));
// ensure we can lock from a shared source // ensure we can lock from a shared source
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token)); EXPECT_TRUE(lock.try_lock_shared(token));
lock.unlock_shared(token); lock.unlock_shared(token);
// promote from upgrade to exclusive lock via move constructor // promote from upgrade to exclusive lock via move constructor
typename Lock::WriteHolder holder5(std::move(holder4)); typename Lock::WriteHolder holder5(std::move(holder4));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token)); EXPECT_FALSE(lock.try_lock_shared(token));
// downgrade exclusive to shared lock via move constructor // downgrade exclusive to shared lock via move constructor
typename Lock::ReadHolder holder6(std::move(holder5)); typename Lock::ReadHolder holder6(std::move(holder5));
// ensure we can lock from another shared source // ensure we can lock from another shared source
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token)); EXPECT_TRUE(lock.try_lock_shared(token));
lock.unlock_shared(token); lock.unlock_shared(token);
} }
{ {
typename Lock::WriteHolder holder(lock); typename Lock::WriteHolder holder(lock);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
} }
...@@ -157,11 +176,13 @@ void runManyReadLocksTestWithTokens() { ...@@ -157,11 +176,13 @@ void runManyReadLocksTestWithTokens() {
vector<SharedMutexToken> tokens; vector<SharedMutexToken> tokens;
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
tokens.emplace_back(); tokens.emplace_back();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(tokens.back())); EXPECT_TRUE(lock.try_lock_shared(tokens.back()));
} }
for (auto& token : tokens) { for (auto& token : tokens) {
lock.unlock_shared(token); lock.unlock_shared(token);
} }
EXPECT_TRUE(lock.try_lock()); EXPECT_TRUE(lock.try_lock());
lock.unlock(); lock.unlock();
} }
...@@ -180,11 +201,13 @@ void runManyReadLocksTestWithoutTokens() { ...@@ -180,11 +201,13 @@ void runManyReadLocksTestWithoutTokens() {
Lock lock; Lock lock;
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared()); EXPECT_TRUE(lock.try_lock_shared());
} }
for (int i = 0; i < 1000; ++i) { for (int i = 0; i < 1000; ++i) {
lock.unlock_shared(); lock.unlock_shared();
} }
EXPECT_TRUE(lock.try_lock()); EXPECT_TRUE(lock.try_lock());
lock.unlock(); lock.unlock();
} }
...@@ -321,14 +344,20 @@ void runBasicUpgradeTest() { ...@@ -321,14 +344,20 @@ void runBasicUpgradeTest() {
typename Lock::Token token1; typename Lock::Token token1;
typename Lock::Token token2; typename Lock::Token token2;
EXPECT_TRUE(lock.eligible_for_lock_upgrade_elision());
lock.lock_upgrade(); lock.lock_upgrade();
EXPECT_FALSE(lock.eligible_for_lock_upgrade_elision());
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1)); EXPECT_TRUE(lock.try_lock_shared(token1));
lock.unlock_shared(token1); lock.unlock_shared(token1);
lock.unlock_upgrade(); lock.unlock_upgrade();
EXPECT_TRUE(lock.eligible_for_lock_upgrade_elision());
lock.lock_upgrade(); lock.lock_upgrade();
lock.unlock_upgrade_and_lock(); lock.unlock_upgrade_and_lock();
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token1)); EXPECT_FALSE(lock.try_lock_shared(token1));
lock.unlock(); lock.unlock();
...@@ -341,6 +370,7 @@ void runBasicUpgradeTest() { ...@@ -341,6 +370,7 @@ void runBasicUpgradeTest() {
lock.lock(); lock.lock();
lock.unlock_and_lock_upgrade(); lock.unlock_and_lock_upgrade();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1)); EXPECT_TRUE(lock.try_lock_shared(token1));
lock.unlock_upgrade(); lock.unlock_upgrade();
lock.unlock_shared(token1); lock.unlock_shared(token1);
......
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