blob: 34ec8beba3066f4e092ad142882282f3d6e33983 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <cstdint>
#include <mutex>
#include <ostream>
#include <thread>
#include <vector>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "kudu/util/atomic.h"
#include "kudu/util/locks.h"
#include "kudu/util/monotime.h"
#include "kudu/util/rw_mutex.h"
#include "kudu/util/test_util.h"
using std::lock_guard;
using std::thread;
using std::try_to_lock;
using std::unique_lock;
using std::vector;
namespace kudu {
class RWMutexTest : public KuduTest,
public ::testing::WithParamInterface<RWMutex::Priority> {
public:
RWMutexTest()
: lock_(GetParam()) {
}
protected:
RWMutex lock_;
};
// Instantiate every test for each kind of RWMutex priority.
INSTANTIATE_TEST_SUITE_P(Priorities, RWMutexTest,
::testing::Values(RWMutex::Priority::PREFER_READING,
RWMutex::Priority::PREFER_WRITING));
// Multi-threaded test that tries to find deadlocks in the RWMutex wrapper.
TEST_P(RWMutexTest, TestDeadlocks) {
uint64_t number_of_writes = 0;
AtomicInt<uint64_t> number_of_reads(0);
AtomicBool done(false);
vector<thread> threads;
// Start several blocking and non-blocking read-write workloads.
for (int i = 0; i < 2; i++) {
threads.emplace_back([&](){
while (!done.Load()) {
lock_guard<RWMutex> l(lock_);
number_of_writes++;
}
});
threads.emplace_back([&](){
while (!done.Load()) {
unique_lock<RWMutex> l(lock_, try_to_lock);
if (l.owns_lock()) {
number_of_writes++;
}
}
});
}
// Start several blocking and non-blocking read-only workloads.
for (int i = 0; i < 2; i++) {
threads.emplace_back([&](){
while (!done.Load()) {
shared_lock<RWMutex> l(lock_);
number_of_reads.Increment();
}
});
threads.emplace_back([&](){
while (!done.Load()) {
shared_lock<RWMutex> l(lock_, try_to_lock);
if (l.owns_lock()) {
number_of_reads.Increment();
}
}
});
}
SleepFor(MonoDelta::FromSeconds(1));
done.Store(true);
for (auto& t : threads) {
t.join();
}
shared_lock<RWMutex> l(lock_);
LOG(INFO) << "Number of writes: " << number_of_writes;
LOG(INFO) << "Number of reads: " << number_of_reads.Load();
}
#ifndef NDEBUG
// Tests that the RWMutex wrapper catches basic usage errors. This checking is
// only enabled in debug builds.
TEST_P(RWMutexTest, TestLockChecking) {
EXPECT_DEATH(({
lock_.ReadLock();
lock_.ReadLock();
}), "already holding lock for reading");
EXPECT_DEATH(({
CHECK(lock_.TryReadLock());
CHECK(lock_.TryReadLock());
}), "already holding lock for reading");
EXPECT_DEATH(({
lock_.ReadLock();
lock_.WriteLock();
}), "already holding lock for reading");
EXPECT_DEATH(({
CHECK(lock_.TryReadLock());
CHECK(lock_.TryWriteLock());
}), "already holding lock for reading");
EXPECT_DEATH(({
lock_.WriteLock();
lock_.ReadLock();
}), "already holding lock for writing");
EXPECT_DEATH(({
CHECK(lock_.TryWriteLock());
CHECK(lock_.TryReadLock());
}), "already holding lock for writing");
EXPECT_DEATH(({
lock_.WriteLock();
lock_.WriteLock();
}), "already holding lock for writing");
EXPECT_DEATH(({
CHECK(lock_.TryWriteLock());
CHECK(lock_.TryWriteLock());
}), "already holding lock for writing");
EXPECT_DEATH(({
lock_.ReadUnlock();
}), "wasn't holding lock for reading");
EXPECT_DEATH(({
lock_.WriteUnlock();
}), "wasn't holding lock for writing");
EXPECT_DEATH(({
lock_.ReadLock();
lock_.WriteUnlock();
}), "already holding lock for reading");
EXPECT_DEATH(({
CHECK(lock_.TryReadLock());
lock_.WriteUnlock();
}), "already holding lock for reading");
EXPECT_DEATH(({
lock_.WriteLock();
lock_.ReadUnlock();
}), "already holding lock for writing");
EXPECT_DEATH(({
CHECK(lock_.TryWriteLock());
lock_.ReadUnlock();
}), "already holding lock for writing");
}
#endif
} // namespace kudu