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 {
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() {
WaitForever ctx;
(void)lockExclusiveImpl(kHasSolo, ctx);
......
......@@ -48,13 +48,19 @@ void runBasicTest() {
SharedMutexToken token2;
SharedMutexToken token3;
EXPECT_TRUE(lock.eligible_for_lock_elision());
EXPECT_TRUE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token1));
lock.unlock();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token2));
lock.lock_shared(token3);
lock.unlock_shared(token3);
......@@ -90,44 +96,57 @@ void runBasicHoldersTest() {
{
// create an exclusive write lock via holder
typename Lock::WriteHolder holder(lock);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token));
// move ownership to another write holder via move constructor
typename Lock::WriteHolder holder2(std::move(holder));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token));
// move ownership to another write holder via assign operator
typename Lock::WriteHolder holder3(nullptr);
holder3 = std::move(holder2);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token));
// downgrade from exclusive to upgrade lock via move constructor
typename Lock::UpgradeHolder holder4(std::move(holder3));
// ensure we can lock from a shared source
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token));
lock.unlock_shared(token);
// promote from upgrade to exclusive lock via move constructor
typename Lock::WriteHolder holder5(std::move(holder4));
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token));
// downgrade exclusive to shared lock via move constructor
typename Lock::ReadHolder holder6(std::move(holder5));
// ensure we can lock from another shared source
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token));
lock.unlock_shared(token);
}
{
typename Lock::WriteHolder holder(lock);
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
}
......@@ -157,11 +176,13 @@ void runManyReadLocksTestWithTokens() {
vector<SharedMutexToken> tokens;
for (int i = 0; i < 1000; ++i) {
tokens.emplace_back();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(tokens.back()));
}
for (auto& token : tokens) {
lock.unlock_shared(token);
}
EXPECT_TRUE(lock.try_lock());
lock.unlock();
}
......@@ -180,11 +201,13 @@ void runManyReadLocksTestWithoutTokens() {
Lock lock;
for (int i = 0; i < 1000; ++i) {
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared());
}
for (int i = 0; i < 1000; ++i) {
lock.unlock_shared();
}
EXPECT_TRUE(lock.try_lock());
lock.unlock();
}
......@@ -321,14 +344,20 @@ void runBasicUpgradeTest() {
typename Lock::Token token1;
typename Lock::Token token2;
EXPECT_TRUE(lock.eligible_for_lock_upgrade_elision());
lock.lock_upgrade();
EXPECT_FALSE(lock.eligible_for_lock_upgrade_elision());
EXPECT_FALSE(lock.eligible_for_lock_elision());
EXPECT_FALSE(lock.try_lock());
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1));
lock.unlock_shared(token1);
lock.unlock_upgrade();
EXPECT_TRUE(lock.eligible_for_lock_upgrade_elision());
lock.lock_upgrade();
lock.unlock_upgrade_and_lock();
EXPECT_FALSE(lock.eligible_for_lock_shared_elision());
EXPECT_FALSE(lock.try_lock_shared(token1));
lock.unlock();
......@@ -341,6 +370,7 @@ void runBasicUpgradeTest() {
lock.lock();
lock.unlock_and_lock_upgrade();
EXPECT_TRUE(lock.eligible_for_lock_shared_elision());
EXPECT_TRUE(lock.try_lock_shared(token1));
lock.unlock_upgrade();
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