blob: 25cd80e6b46d8ec32a28e8ff8806031800d900b6 [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.
*/
#pragma once
#include <atomic>
#include <functional>
#include <thread>
#include <unordered_set>
#include <vector>
#include <boost/noncopyable.hpp>
#include <semaphore.h>
#include <errno.h>
#include <assert.h>
#include <glog/logging.h>
#include <folly/ScopeGuard.h>
#include <folly/detail/CacheLocality.h>
#include <folly/detail/Futex.h>
namespace folly {
namespace test {
// This is ugly, but better perf for DeterministicAtomic translates
// directly to more states explored and tested
#define FOLLY_TEST_DSCHED_VLOG(msg...) \
do { \
if (false) { \
VLOG(2) << std::hex << std::this_thread::get_id() << ": " << msg; \
} \
} while (false)
/**
* DeterministicSchedule coordinates the inter-thread communication of a
* set of threads under test, so that despite concurrency the execution is
* the same every time. It works by stashing a reference to the schedule
* in a thread-local variable, then blocking all but one thread at a time.
*
* In order for DeterministicSchedule to work, it needs to intercept
* all inter-thread communication. To do this you should use
* DeterministicAtomic<T> instead of std::atomic<T>, create threads
* using DeterministicSchedule::thread() instead of the std::thread
* constructor, DeterministicSchedule::join(thr) instead of thr.join(),
* and access semaphores via the helper functions in DeterministicSchedule.
* Locks are not yet supported, although they would be easy to add with
* the same strategy as the mapping of sem_wait.
*
* The actual schedule is defined by a function from n -> [0,n). At
* each step, the function will be given the number of active threads
* (n), and it returns the index of the thread that should be run next.
* Invocations of the scheduler function will be serialized, but will
* occur from multiple threads. A good starting schedule is uniform(0).
*/
class DeterministicSchedule : boost::noncopyable {
public:
/**
* Arranges for the current thread (and all threads created by
* DeterministicSchedule::thread on a thread participating in this
* schedule) to participate in a deterministic schedule.
*/
explicit DeterministicSchedule(const std::function<int(int)>& scheduler);
/** Completes the schedule. */
~DeterministicSchedule();
/**
* Returns a scheduling function that randomly chooses one of the
* runnable threads at each step, with no history. This implements
* a schedule that is equivalent to one in which the steps between
* inter-thread communication are random variables following a poisson
* distribution.
*/
static std::function<int(int)> uniform(long seed);
/**
* Returns a scheduling function that chooses a subset of the active
* threads and randomly chooses a member of the subset as the next
* runnable thread. The subset is chosen with size n, and the choice
* is made every m steps.
*/
static std::function<int(int)> uniformSubset(long seed,
int n = 2,
int m = 64);
/** Obtains permission for the current thread to perform inter-thread
* communication. */
static void beforeSharedAccess();
/** Releases permission for the current thread to perform inter-thread
* communication. */
static void afterSharedAccess();
/** Launches a thread that will participate in the same deterministic
* schedule as the current thread. */
template <typename Func, typename... Args>
static inline std::thread thread(Func&& func, Args&&... args) {
// TODO: maybe future versions of gcc will allow forwarding to thread
auto sched = tls_sched;
auto sem = sched ? sched->beforeThreadCreate() : nullptr;
auto child = std::thread([=](Args... a) {
if (sched) {
sched->afterThreadCreate(sem);
beforeSharedAccess();
FOLLY_TEST_DSCHED_VLOG("running");
afterSharedAccess();
}
SCOPE_EXIT {
if (sched) {
sched->beforeThreadExit();
}
};
func(a...);
}, args...);
if (sched) {
beforeSharedAccess();
sched->active_.insert(child.get_id());
FOLLY_TEST_DSCHED_VLOG("forked " << std::hex << child.get_id());
afterSharedAccess();
}
return child;
}
/** Calls child.join() as part of a deterministic schedule. */
static void join(std::thread& child);
/** Calls sem_post(sem) as part of a deterministic schedule. */
static void post(sem_t* sem);
/** Calls sem_trywait(sem) as part of a deterministic schedule, returning
* true on success and false on transient failure. */
static bool tryWait(sem_t* sem);
/** Calls sem_wait(sem) as part of a deterministic schedule. */
static void wait(sem_t* sem);
/** Used scheduler_ to get a random number b/w [0, n). If tls_sched is
* not set-up it falls back to std::rand() */
static int getRandNumber(int n);
/** Deterministic implemencation of getcpu */
static int getcpu(unsigned* cpu, unsigned* node, void* unused);
private:
static FOLLY_TLS sem_t* tls_sem;
static FOLLY_TLS DeterministicSchedule* tls_sched;
static FOLLY_TLS unsigned tls_threadId;
std::function<int(int)> scheduler_;
std::vector<sem_t*> sems_;
std::unordered_set<std::thread::id> active_;
unsigned nextThreadId_;
sem_t* beforeThreadCreate();
void afterThreadCreate(sem_t*);
void beforeThreadExit();
};
/**
* DeterministicAtomic<T> is a drop-in replacement std::atomic<T> that
* cooperates with DeterministicSchedule.
*/
template <typename T>
struct DeterministicAtomic {
std::atomic<T> data;
DeterministicAtomic() = default;
~DeterministicAtomic() = default;
DeterministicAtomic(DeterministicAtomic<T> const&) = delete;
DeterministicAtomic<T>& operator=(DeterministicAtomic<T> const&) = delete;
constexpr /* implicit */ DeterministicAtomic(T v) noexcept : data(v) {}
bool is_lock_free() const noexcept { return data.is_lock_free(); }
bool compare_exchange_strong(
T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
auto orig = v0;
bool rv = data.compare_exchange_strong(v0, v1, mo);
FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_strong(" << std::hex
<< orig << ", " << std::hex << v1 << ") -> "
<< rv << "," << std::hex << v0);
DeterministicSchedule::afterSharedAccess();
return rv;
}
bool compare_exchange_weak(
T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
auto orig = v0;
bool rv = data.compare_exchange_weak(v0, v1, mo);
FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_weak(" << std::hex << orig
<< ", " << std::hex << v1 << ") -> " << rv
<< "," << std::hex << v0);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data.exchange(v, mo);
FOLLY_TEST_DSCHED_VLOG(this << ".exchange(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
/* implicit */ operator T() const noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
FOLLY_TEST_DSCHED_VLOG(this << "() -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data.load(mo);
FOLLY_TEST_DSCHED_VLOG(this << ".load() -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data = v);
FOLLY_TEST_DSCHED_VLOG(this << " = " << std::hex << v);
DeterministicSchedule::afterSharedAccess();
return rv;
}
void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
data.store(v, mo);
FOLLY_TEST_DSCHED_VLOG(this << ".store(" << std::hex << v << ")");
DeterministicSchedule::afterSharedAccess();
}
T operator++() noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = ++data;
FOLLY_TEST_DSCHED_VLOG(this << " pre++ -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator++(int postDummy) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data++;
FOLLY_TEST_DSCHED_VLOG(this << " post++ -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator--() noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = --data;
FOLLY_TEST_DSCHED_VLOG(this << " pre-- -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator--(int postDummy) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data--;
FOLLY_TEST_DSCHED_VLOG(this << " post-- -> " << std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator+=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data += v);
FOLLY_TEST_DSCHED_VLOG(this << " += " << std::hex << v << " -> " << std::hex
<< rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T fetch_add(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
data += v;
FOLLY_TEST_DSCHED_VLOG(this << ".fetch_add(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator-=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data -= v);
FOLLY_TEST_DSCHED_VLOG(this << " -= " << std::hex << v << " -> " << std::hex
<< rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T fetch_sub(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
data -= v;
FOLLY_TEST_DSCHED_VLOG(this << ".fetch_sub(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator&=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data &= v);
FOLLY_TEST_DSCHED_VLOG(this << " &= " << std::hex << v << " -> " << std::hex
<< rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T fetch_and(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
data &= v;
FOLLY_TEST_DSCHED_VLOG(this << ".fetch_and(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator|=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data |= v);
FOLLY_TEST_DSCHED_VLOG(this << " |= " << std::hex << v << " -> " << std::hex
<< rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T fetch_or(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
data |= v;
FOLLY_TEST_DSCHED_VLOG(this << ".fetch_or(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T operator^=(T v) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = (data ^= v);
FOLLY_TEST_DSCHED_VLOG(this << " ^= " << std::hex << v << " -> " << std::hex
<< rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
T fetch_xor(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
DeterministicSchedule::beforeSharedAccess();
T rv = data;
data ^= v;
FOLLY_TEST_DSCHED_VLOG(this << ".fetch_xor(" << std::hex << v << ") -> "
<< std::hex << rv);
DeterministicSchedule::afterSharedAccess();
return rv;
}
};
}
} // namespace folly::test
/* Specialization declarations */
namespace folly {
namespace detail {
template <>
int Futex<test::DeterministicAtomic>::futexWake(int count, uint32_t wakeMask);
template <>
FutexResult Futex<test::DeterministicAtomic>::futexWaitImpl(
uint32_t expected,
std::chrono::time_point<std::chrono::system_clock>* absSystemTime,
std::chrono::time_point<std::chrono::steady_clock>* absSteadyTime,
uint32_t waitMask);
template <>
Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc(
size_t numStripes);
}
} // namespace folly::detail