Commit e4ef6050 authored by Adam Simpkins's avatar Adam Simpkins Committed by JoelMarcey

SpinLock improvements

Summary:
This makes several improvements to the SpinLock code:

- Add a SpinLock implementation using pthread_spinlock_t.  On non-x86_64
platforms this is preferred over the pthread_mutex_t implementation
when available.

- For the pthread implementations, throw std::system_error on error,
rather than completely aborting the program using glog's CHECK()
macros.

- Update the pthread_mutex_t implementation to call
pthread_mutex_destroy() on destruction.

- Always unit test all implementations that can be compiled on the
current build platform, even though only a single implementation will
be selected as folly::SpinLock.  This way x86_64 builds will still
unit test the pthread-based implementations.

Test Plan: Ran the unit tests.

Reviewed By: seanc@fb.com

Subscribers: trunkagent, doug, net-systems@, exa, folly-diffs@

FB internal diff: D1735770

Signature: t1:1735770:1418445953:b238aa8fb835a8d55e6e98e20c4615ae1938b98f
parent 6b3d35fc
...@@ -55,6 +55,7 @@ nobase_follyinclude_HEADERS = \ ...@@ -55,6 +55,7 @@ nobase_follyinclude_HEADERS = \
detail/MemoryIdler.h \ detail/MemoryIdler.h \
detail/MPMCPipelineDetail.h \ detail/MPMCPipelineDetail.h \
detail/SlowFingerprint.h \ detail/SlowFingerprint.h \
detail/SpinLock.h \
detail/Stats.h \ detail/Stats.h \
detail/ThreadLocalDetail.h \ detail/ThreadLocalDetail.h \
detail/UncaughtExceptionCounter.h \ detail/UncaughtExceptionCounter.h \
......
...@@ -16,107 +16,34 @@ ...@@ -16,107 +16,34 @@
#pragma once #pragma once
#include <boost/noncopyable.hpp> #include <folly/detail/SpinLockImpl.h>
#include <folly/Portability.h>
// This is a wrapper SpinLock implementation that works around the
// x64 limitation of the base folly MicroSpinLock. If that is available, this
// simply thinly wraps it. Otherwise, it uses the simplest analog available on
// iOS (or 32-bit Mac) or, failing that, POSIX (on Android et. al.)
#if __x86_64__
#include <folly/SmallLocks.h>
namespace folly { namespace folly {
class SpinLock { #if __x86_64__
public: typedef SpinLockMslImpl SpinLock;
FOLLY_ALWAYS_INLINE SpinLock() {
lock_.init();
}
FOLLY_ALWAYS_INLINE void lock() const {
lock_.lock();
}
FOLLY_ALWAYS_INLINE void unlock() const {
lock_.unlock();
}
FOLLY_ALWAYS_INLINE bool trylock() const {
return lock_.try_lock();
}
private:
mutable folly::MicroSpinLock lock_;
};
}
#elif __APPLE__ #elif __APPLE__
#include <libkern/OSAtomic.h> typedef SpinLockAppleImpl SpinLock;
#elif __ANDROID__
namespace folly { typedef SpinLockPthreadMutexImpl SpinLock;
class SpinLock {
public:
FOLLY_ALWAYS_INLINE SpinLock() : lock_(0) {}
FOLLY_ALWAYS_INLINE void lock() const {
OSSpinLockLock(&lock_);
}
FOLLY_ALWAYS_INLINE void unlock() const {
OSSpinLockUnlock(&lock_);
}
FOLLY_ALWAYS_INLINE bool trylock() const {
return OSSpinLockTry(&lock_);
}
private:
mutable OSSpinLock lock_;
};
}
#else #else
#include <pthread.h> typedef SpinLockPthreadImpl SpinLock;
#include <glog/logging.h>
namespace folly {
class SpinLock {
public:
FOLLY_ALWAYS_INLINE SpinLock() {
pthread_mutex_init(&lock_, nullptr);
}
void lock() const {
int rc = pthread_mutex_lock(&lock_);
CHECK_EQ(0, rc);
}
FOLLY_ALWAYS_INLINE void unlock() const {
int rc = pthread_mutex_unlock(&lock_);
CHECK_EQ(0, rc);
}
FOLLY_ALWAYS_INLINE bool trylock() const {
int rc = pthread_mutex_trylock(&lock_);
CHECK_GE(rc, 0);
return rc == 0;
}
private:
mutable pthread_mutex_t lock_;
};
}
#endif #endif
namespace folly { template <typename LOCK>
class SpinLockGuardImpl : private boost::noncopyable {
class SpinLockGuard : private boost::noncopyable {
public: public:
FOLLY_ALWAYS_INLINE explicit SpinLockGuard(SpinLock& lock) : FOLLY_ALWAYS_INLINE explicit SpinLockGuardImpl(LOCK& lock) :
lock_(lock) { lock_(lock) {
lock_.lock(); lock_.lock();
} }
FOLLY_ALWAYS_INLINE ~SpinLockGuard() { FOLLY_ALWAYS_INLINE ~SpinLockGuardImpl() {
lock_.unlock(); lock_.unlock();
} }
private: private:
SpinLock& lock_; LOCK& lock_;
}; };
typedef SpinLockGuardImpl<SpinLock> SpinLockGuard;
} }
/*
* Copyright 2014 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.
*/
#pragma once
/*
* This class provides a few spin lock implementations, depending on the
* platform. folly/SpinLock.h will select one of these as the folly::SpinLock
* implementation.
*
* The main reason we keep these separated out here is so that we can run unit
* tests for all supported spin lock implementations, even though only one will
* be selected as the actual folly::SpinLock implemenatation for any given
* platform.
*/
#include <boost/noncopyable.hpp>
#include <folly/Portability.h>
#if __x86_64__
#include <folly/SmallLocks.h>
namespace folly {
class SpinLockMslImpl {
public:
FOLLY_ALWAYS_INLINE SpinLockMslImpl() {
lock_.init();
}
FOLLY_ALWAYS_INLINE void lock() const {
lock_.lock();
}
FOLLY_ALWAYS_INLINE void unlock() const {
lock_.unlock();
}
FOLLY_ALWAYS_INLINE bool trylock() const {
return lock_.try_lock();
}
private:
mutable folly::MicroSpinLock lock_;
};
}
#endif // __x86_64__
#if __APPLE__
#include <libkern/OSAtomic.h>
namespace folly {
class SpinLockAppleImpl {
public:
FOLLY_ALWAYS_INLINE SpinLockAppleImpl() : lock_(0) {}
FOLLY_ALWAYS_INLINE void lock() const {
OSSpinLockLock(&lock_);
}
FOLLY_ALWAYS_INLINE void unlock() const {
OSSpinLockUnlock(&lock_);
}
FOLLY_ALWAYS_INLINE bool trylock() const {
return OSSpinLockTry(&lock_);
}
private:
mutable OSSpinLock lock_;
};
}
#endif // __APPLE__
#include <pthread.h>
#include <folly/Exception.h>
#if !__ANDROID__ && !__APPLE__
// Apple and Android systems don't have pthread_spinlock_t, so we can't support
// this version on those platforms.
namespace folly {
class SpinLockPthreadImpl {
public:
FOLLY_ALWAYS_INLINE SpinLockPthreadImpl() {
int rc = pthread_spin_init(&lock_, PTHREAD_PROCESS_PRIVATE);
checkPosixError(rc, "failed to initialize spinlock");
}
FOLLY_ALWAYS_INLINE ~SpinLockPthreadImpl() {
pthread_spin_destroy(&lock_);
}
void lock() const {
int rc = pthread_spin_lock(&lock_);
checkPosixError(rc, "error locking spinlock");
}
FOLLY_ALWAYS_INLINE void unlock() const {
int rc = pthread_spin_unlock(&lock_);
checkPosixError(rc, "error unlocking spinlock");
}
FOLLY_ALWAYS_INLINE bool trylock() const {
int rc = pthread_spin_trylock(&lock_);
if (rc == 0) {
return true;
} else if (rc == EBUSY) {
return false;
}
throwSystemErrorExplicit(rc, "spinlock trylock error");
}
private:
mutable pthread_spinlock_t lock_;
};
}
#endif // !__ANDROID__ && !__APPLE__
namespace folly {
class SpinLockPthreadMutexImpl {
public:
FOLLY_ALWAYS_INLINE SpinLockPthreadMutexImpl() {
int rc = pthread_mutex_init(&lock_, nullptr);
checkPosixError(rc, "failed to initialize mutex");
}
FOLLY_ALWAYS_INLINE ~SpinLockPthreadMutexImpl() {
pthread_mutex_destroy(&lock_);
}
void lock() const {
int rc = pthread_mutex_lock(&lock_);
checkPosixError(rc, "error locking mutex");
}
FOLLY_ALWAYS_INLINE void unlock() const {
int rc = pthread_mutex_unlock(&lock_);
checkPosixError(rc, "error unlocking mutex");
}
FOLLY_ALWAYS_INLINE bool trylock() const {
int rc = pthread_mutex_trylock(&lock_);
if (rc == 0) {
return true;
} else if (rc == EBUSY) {
return false;
}
throwSystemErrorExplicit(rc, "mutex trylock error");
}
private:
mutable pthread_mutex_t lock_;
};
}
...@@ -18,101 +18,146 @@ ...@@ -18,101 +18,146 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <thread> #include <thread>
using folly::SpinLock; using folly::SpinLockGuardImpl;
using folly::SpinLockGuard;
namespace { namespace {
template <typename LOCK>
struct LockedVal { struct LockedVal {
int ar[1024]; int ar[1024];
SpinLock lock; LOCK lock;
LockedVal() { LockedVal() {
memset(ar, 0, sizeof ar); memset(ar, 0, sizeof ar);
} }
}; };
LockedVal v; template <typename LOCK>
void splock_test() { void spinlockTestThread(LockedVal<LOCK>* v) {
const int max = 1000; const int max = 1000;
unsigned int seed = (uintptr_t)pthread_self(); unsigned int seed = (uintptr_t)pthread_self();
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
asm("pause"); asm("pause");
SpinLockGuard g(v.lock); SpinLockGuardImpl<LOCK> g(v->lock);
int first = v.ar[0]; int first = v->ar[0];
for (size_t i = 1; i < sizeof v.ar / sizeof i; ++i) { for (size_t i = 1; i < sizeof v->ar / sizeof i; ++i) {
EXPECT_EQ(first, v.ar[i]); EXPECT_EQ(first, v->ar[i]);
} }
int byte = rand_r(&seed); int byte = rand_r(&seed);
memset(v.ar, char(byte), sizeof v.ar); memset(v->ar, char(byte), sizeof v->ar);
} }
} }
template <typename LOCK>
struct TryLockState { struct TryLockState {
SpinLock lock1; LOCK lock1;
SpinLock lock2; LOCK lock2;
bool locked{false}; bool locked{false};
uint64_t obtained{0}; uint64_t obtained{0};
uint64_t failed{0}; uint64_t failed{0};
}; };
TryLockState tryState;
void trylock_test() { template <typename LOCK>
const int max = 1000; void trylockTestThread(TryLockState<LOCK>* state, int count) {
while (true) { while (true) {
asm("pause"); asm("pause");
SpinLockGuard g(tryState.lock1); SpinLockGuardImpl<LOCK> g(state->lock1);
if (tryState.obtained >= max) { if (state->obtained >= count) {
break; break;
} }
bool ret = tryState.lock2.trylock(); bool ret = state->lock2.trylock();
EXPECT_NE(tryState.locked, ret); EXPECT_NE(state->locked, ret);
if (ret) { if (ret) {
// We got lock2. // We got lock2.
++tryState.obtained; ++state->obtained;
tryState.locked = true; state->locked = true;
// Release lock1 and let other threads try to obtain lock2 // Release lock1 and wait until at least one other thread fails to
tryState.lock1.unlock(); // obtain the lock2 before continuing.
auto oldFailed = state->failed;
while (state->failed == oldFailed && state->obtained < count) {
state->lock1.unlock();
asm("pause"); asm("pause");
tryState.lock1.lock(); state->lock1.lock();
}
tryState.locked = false; state->locked = false;
tryState.lock2.unlock(); state->lock2.unlock();
} else { } else {
++tryState.failed; ++state->failed;
} }
} }
} }
} // unnamed namespace template <typename LOCK>
void correctnessTest() {
TEST(SpinLock, Correctness) {
int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2; int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2;
std::vector<std::thread> threads; std::vector<std::thread> threads;
LockedVal<LOCK> v;
for (int i = 0; i < nthrs; ++i) { for (int i = 0; i < nthrs; ++i) {
threads.push_back(std::thread(splock_test)); threads.push_back(std::thread(spinlockTestThread<LOCK>, &v));
} }
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
} }
TEST(SpinLock, TryLock) { template <typename LOCK>
int nthrs = sysconf(_SC_NPROCESSORS_ONLN) * 2; void trylockTest() {
int nthrs = sysconf(_SC_NPROCESSORS_ONLN) + 4;
std::vector<std::thread> threads; std::vector<std::thread> threads;
TryLockState<LOCK> state;
int count = 100;
for (int i = 0; i < nthrs; ++i) { for (int i = 0; i < nthrs; ++i) {
threads.push_back(std::thread(trylock_test)); threads.push_back(std::thread(trylockTestThread<LOCK>, &state, count));
} }
for (auto& t : threads) { for (auto& t : threads) {
t.join(); t.join();
} }
EXPECT_EQ(1000, tryState.obtained); EXPECT_EQ(count, state.obtained);
EXPECT_GT(tryState.failed, 0); // Each time the code obtains lock2 it waits for another thread to fail
LOG(INFO) << "failed count: " << tryState.failed; // to acquire it. The only time this might not happen is on the very last
// loop when no other threads are left.
EXPECT_GE(state.failed + 1, state.obtained);
}
} // unnamed namespace
#if __x86_64__
TEST(SpinLock, MslCorrectness) {
correctnessTest<folly::SpinLockMslImpl>();
}
TEST(SpinLock, MslTryLock) {
trylockTest<folly::SpinLockMslImpl>();
}
#endif
#if __APPLE__
TEST(SpinLock, AppleCorrectness) {
correctnessTest<folly::SpinLockAppleImpl>();
}
TEST(SpinLock, AppleTryLock) {
trylockTest<folly::SpinLockAppleImpl>();
}
#endif
#if !__ANDROID__
TEST(SpinLock, PthreadCorrectness) {
correctnessTest<folly::SpinLockPthreadImpl>();
}
TEST(SpinLock, PthreadTryLock) {
trylockTest<folly::SpinLockPthreadImpl>();
}
#endif
TEST(SpinLock, MutexCorrectness) {
correctnessTest<folly::SpinLockPthreadMutexImpl>();
}
TEST(SpinLock, MutexTryLock) {
trylockTest<folly::SpinLockPthreadMutexImpl>();
} }
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