blob: 059e41c063a38d0148b8a2936687269cba947f96 [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* 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 <folly/SmallLocks.h>
#include <cassert>
#include <cstdio>
#include <mutex>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include <thread>
#include <gtest/gtest.h>
using std::string;
using folly::MicroSpinLock;
using folly::PicoSpinLock;
using folly::MSLGuard;
namespace {
struct LockedVal {
int ar[1024];
MicroSpinLock lock;
LockedVal() {
lock.init();
memset(ar, 0, sizeof ar);
}
};
// Compile time test for packed struct support (requires that both of
// these classes are POD).
FOLLY_PACK_PUSH
struct ignore1 { MicroSpinLock msl; int16_t foo; } FOLLY_PACK_ATTR;
struct ignore2 { PicoSpinLock<uint32_t> psl; int16_t foo; } FOLLY_PACK_ATTR;
static_assert(sizeof(ignore1) == 3, "Size check failed");
static_assert(sizeof(ignore2) == 6, "Size check failed");
static_assert(sizeof(MicroSpinLock) == 1, "Size check failed");
FOLLY_PACK_POP
LockedVal v;
void splock_test() {
const int max = 1000;
unsigned int seed = (uintptr_t)pthread_self();
for (int i = 0; i < max; i++) {
folly::asm_pause();
MSLGuard g(v.lock);
int first = v.ar[0];
for (size_t i = 1; i < sizeof v.ar / sizeof i; ++i) {
EXPECT_EQ(first, v.ar[i]);
}
int byte = rand_r(&seed);
memset(v.ar, char(byte), sizeof v.ar);
}
}
template<class T> struct PslTest {
PicoSpinLock<T> lock;
PslTest() { lock.init(); }
void doTest() {
T ourVal = rand() % (T(1) << (sizeof(T) * 8 - 1));
for (int i = 0; i < 10000; ++i) {
std::lock_guard<PicoSpinLock<T>> guard(lock);
lock.setData(ourVal);
for (int n = 0; n < 10; ++n) {
folly::asm_volatile_pause();
EXPECT_EQ(lock.getData(), ourVal);
}
}
}
};
template<class T>
void doPslTest() {
PslTest<T> testObj;
const int nthrs = 17;
std::vector<std::thread> threads;
for (int i = 0; i < nthrs; ++i) {
threads.push_back(std::thread(&PslTest<T>::doTest, &testObj));
}
for (auto& t : threads) {
t.join();
}
}
struct TestClobber {
TestClobber() {
lock_.init();
}
void go() {
std::lock_guard<MicroSpinLock> g(lock_);
// This bug depends on gcc register allocation and is very sensitive. We
// have to use DCHECK instead of EXPECT_*.
DCHECK(!lock_.try_lock());
}
private:
MicroSpinLock lock_;
};
}
TEST(SmallLocks, SpinLockCorrectness) {
EXPECT_EQ(sizeof(MicroSpinLock), 1);
int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2;
std::vector<std::thread> threads;
for (int i = 0; i < nthrs; ++i) {
threads.push_back(std::thread(splock_test));
}
for (auto& t : threads) {
t.join();
}
}
TEST(SmallLocks, PicoSpinCorrectness) {
doPslTest<int16_t>();
doPslTest<uint16_t>();
doPslTest<int32_t>();
doPslTest<uint32_t>();
doPslTest<int64_t>();
doPslTest<uint64_t>();
}
TEST(SmallLocks, PicoSpinSigned) {
typedef PicoSpinLock<int16_t,0> Lock;
Lock val;
val.init(-4);
EXPECT_EQ(val.getData(), -4);
{
std::lock_guard<Lock> guard(val);
EXPECT_EQ(val.getData(), -4);
val.setData(-8);
EXPECT_EQ(val.getData(), -8);
}
EXPECT_EQ(val.getData(), -8);
}
TEST(SmallLocks, RegClobber) {
TestClobber().go();
}