blob: b31b5c3b6f6507421667f8e4bc528d0bba17a7c7 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed 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 "android/base/synchronization/Lock.h"
#include "android/base/testing/TestThread.h"
#include "android/base/testing/Utils.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <random>
#include <utility>
#include <vector>
namespace android {
namespace base {
// On windows versions prior to Vista,
// this should use the fallback implementation.
// It should work for both 32 and 64 bit versions of the unit test
// (WINAPI (which changes calling convention)
// was needed to make the 32 bit version work)
TEST(ReadWriteLock, init) {
ReadWriteLock rwl;
}
// basic lock/unlock for readers
TEST(ReadWriteLock, readLockBasic) {
ReadWriteLock rwl;
rwl.lockRead();
rwl.unlockRead();
}
// basic lock/unlock for writer
TEST(ReadWriteLock, writeLockBasic) {
ReadWriteLock rwl;
rwl.lockWrite();
rwl.unlockWrite();
}
// check we can lock reader multiple times from the same thread
TEST(ReadWriteLock, readLockMultiReadSingleThread) {
ReadWriteLock rwl;
rwl.lockRead();
rwl.lockRead();
rwl.lockRead();
rwl.lockRead();
rwl.unlockRead();
rwl.unlockRead();
rwl.unlockRead();
rwl.unlockRead();
}
// Write lock tests.
// RWThreadParams class + thread fn inspired by Lock_unittest
// Package up data being modified + lock class
struct RWThreadParams {
RWThreadParams() : mutex(), counter(0), read_counter(0) {}
ReadWriteLock mutex;
int counter;
int read_counter;
};
static void* writeThreadFunction(void* param) {
RWThreadParams* p = static_cast<RWThreadParams*>(param);
p->mutex.lockWrite();
p->counter++;
p->mutex.unlockWrite();
return NULL;
}
static void* readThreadFunction(void* param) {
RWThreadParams* p = static_cast<RWThreadParams*>(param);
p->mutex.lockRead();
p->read_counter++;
p->mutex.unlockRead();
return NULL;
}
// basic synchronized write / read tests
// write: just like for Lock
TEST(ReadWriteLock, SyncWrite) {
ReadWriteLock rwl;
const size_t kNumWriteThreads = 2000;
std::vector<TestThread> write_threads;
write_threads.reserve(kNumWriteThreads);
RWThreadParams p;
// Create and launch all threads.
for (size_t i = 0; i < kNumWriteThreads; i++) {
write_threads.emplace_back(writeThreadFunction, &p);
}
// Wait until their completion.
for (size_t i = 0; i < kNumWriteThreads; ++i) {
write_threads[i].join();
}
EXPECT_EQ(static_cast<int>(kNumWriteThreads), p.counter);
}
// Have half of the threads read/write.
// Launch them in various ways (interleaved,
// all writes first, all reads first, random)
TEST(ReadWriteLock, SyncReadWrite) {
const size_t kNumThreads = 1000;
std::vector<TestThread> write_threads;
std::vector<TestThread> read_threads;
write_threads.reserve(kNumThreads);
read_threads.reserve(kNumThreads);
RWThreadParams p;
// Create and launch all threads.
for (size_t i = 0; i < kNumThreads; ++i) {
write_threads.emplace_back(writeThreadFunction, &p);
read_threads.emplace_back(readThreadFunction, &p);
}
// Wait until their completion.
for (size_t n = 0; n < kNumThreads; ++n) {
write_threads[n].join();
read_threads[n].join();
}
EXPECT_EQ(static_cast<int>(kNumThreads), p.counter);
EXPECT_GE(static_cast<int>(kNumThreads), p.read_counter);
p.counter = 0;
p.read_counter = 0;
// Create and launch all threads.
write_threads.clear();
read_threads.clear();
for (size_t i = 0; i < kNumThreads; ++i) {
write_threads.emplace_back(writeThreadFunction, &p);
}
for (size_t i = 0; i < kNumThreads; ++i) {
read_threads.emplace_back(readThreadFunction, &p);
}
// Wait until their completion.
for (size_t i = 0; i < kNumThreads; ++i) {
write_threads[i].join();
read_threads[i].join();
}
EXPECT_EQ(static_cast<int>(kNumThreads), p.counter);
EXPECT_GE(static_cast<int>(kNumThreads), p.read_counter);
p.counter = 0;
p.read_counter = 0;
// Create and launch all threads.
write_threads.clear();
read_threads.clear();
for (size_t i = 0; i < kNumThreads; ++i) {
read_threads.emplace_back(readThreadFunction, &p);
}
for (size_t i = 0; i < kNumThreads; ++i) {
write_threads.emplace_back(writeThreadFunction, &p);
}
// Wait until their completion.
for (size_t i = 0; i < kNumThreads; ++i) {
write_threads[i].join();
read_threads[i].join();
}
EXPECT_EQ(static_cast<int>(kNumThreads), p.counter);
EXPECT_GE(static_cast<int>(kNumThreads), p.read_counter);
}
TEST(ReadWriteLock, SyncReadWriteRandom) {
const size_t kTrials = 100;
const size_t kNumThreadsPerTrial = 100;
RWThreadParams p;
std::default_random_engine generator;
// At least one writer and reader
std::uniform_int_distribution<int> writers(1, kNumThreadsPerTrial - 1);
std::vector<TestThread*> threads(kNumThreadsPerTrial);
for (size_t i = 0; i < kTrials; i++) {
p.counter = 0;
p.read_counter = 0;
size_t num_writers = writers(generator);
size_t num_readers = kNumThreadsPerTrial - num_writers;
std::vector<std::pair<int, bool> > indices_writers;
for (size_t i = 0; i < kNumThreadsPerTrial; i++) {
std::pair<int, bool> item;
item.first = i;
item.second = i < num_writers;
indices_writers.push_back(item);
}
std::random_shuffle(indices_writers.begin(), indices_writers.end());
for (size_t i = 0; i < kNumThreadsPerTrial; i++) {
if (indices_writers[i].second) {
threads[indices_writers[i].first] =
new TestThread(writeThreadFunction, &p);
} else {
threads[indices_writers[i].first] =
new TestThread(readThreadFunction, &p);
}
}
for (size_t i = 0; i < kNumThreadsPerTrial; i++) {
threads[i]->join();
delete threads[i];
}
EXPECT_EQ(num_writers, p.counter);
// Simultaneous reader threads can increment old versions
// of read_counter, so this is allowed to be >=.
EXPECT_GE(num_readers, (size_t)p.read_counter);
}
}
} // namespace base
} // namespace android