2 * Copyright 2016 Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 #include <boost/noncopyable.hpp>
22 #include <glog/logging.h>
23 #include <semaphore.h>
28 #include <unordered_set>
31 #include <folly/ScopeGuard.h>
32 #include <folly/detail/CacheLocality.h>
33 #include <folly/detail/Futex.h>
38 // This is ugly, but better perf for DeterministicAtomic translates
39 // directly to more states explored and tested
40 #define FOLLY_TEST_DSCHED_VLOG(...) \
43 VLOG(2) << std::hex << std::this_thread::get_id() << ": " \
49 * DeterministicSchedule coordinates the inter-thread communication of a
50 * set of threads under test, so that despite concurrency the execution is
51 * the same every time. It works by stashing a reference to the schedule
52 * in a thread-local variable, then blocking all but one thread at a time.
54 * In order for DeterministicSchedule to work, it needs to intercept
55 * all inter-thread communication. To do this you should use
56 * DeterministicAtomic<T> instead of std::atomic<T>, create threads
57 * using DeterministicSchedule::thread() instead of the std::thread
58 * constructor, DeterministicSchedule::join(thr) instead of thr.join(),
59 * and access semaphores via the helper functions in DeterministicSchedule.
60 * Locks are not yet supported, although they would be easy to add with
61 * the same strategy as the mapping of sem_wait.
63 * The actual schedule is defined by a function from n -> [0,n). At
64 * each step, the function will be given the number of active threads
65 * (n), and it returns the index of the thread that should be run next.
66 * Invocations of the scheduler function will be serialized, but will
67 * occur from multiple threads. A good starting schedule is uniform(0).
69 class DeterministicSchedule : boost::noncopyable {
72 * Arranges for the current thread (and all threads created by
73 * DeterministicSchedule::thread on a thread participating in this
74 * schedule) to participate in a deterministic schedule.
76 explicit DeterministicSchedule(const std::function<int(int)>& scheduler);
78 /** Completes the schedule. */
79 ~DeterministicSchedule();
82 * Returns a scheduling function that randomly chooses one of the
83 * runnable threads at each step, with no history. This implements
84 * a schedule that is equivalent to one in which the steps between
85 * inter-thread communication are random variables following a poisson
88 static std::function<int(int)> uniform(long seed);
91 * Returns a scheduling function that chooses a subset of the active
92 * threads and randomly chooses a member of the subset as the next
93 * runnable thread. The subset is chosen with size n, and the choice
94 * is made every m steps.
96 static std::function<int(int)> uniformSubset(long seed,
100 /** Obtains permission for the current thread to perform inter-thread
102 static void beforeSharedAccess();
104 /** Releases permission for the current thread to perform inter-thread
106 static void afterSharedAccess();
108 /** Launches a thread that will participate in the same deterministic
109 * schedule as the current thread. */
110 template <typename Func, typename... Args>
111 static inline std::thread thread(Func&& func, Args&&... args) {
112 // TODO: maybe future versions of gcc will allow forwarding to thread
113 auto sched = tls_sched;
114 auto sem = sched ? sched->beforeThreadCreate() : nullptr;
115 auto child = std::thread([=](Args... a) {
117 sched->afterThreadCreate(sem);
118 beforeSharedAccess();
119 FOLLY_TEST_DSCHED_VLOG("running");
124 sched->beforeThreadExit();
130 beforeSharedAccess();
131 sched->active_.insert(child.get_id());
132 FOLLY_TEST_DSCHED_VLOG("forked " << std::hex << child.get_id());
138 /** Calls child.join() as part of a deterministic schedule. */
139 static void join(std::thread& child);
141 /** Calls sem_post(sem) as part of a deterministic schedule. */
142 static void post(sem_t* sem);
144 /** Calls sem_trywait(sem) as part of a deterministic schedule, returning
145 * true on success and false on transient failure. */
146 static bool tryWait(sem_t* sem);
148 /** Calls sem_wait(sem) as part of a deterministic schedule. */
149 static void wait(sem_t* sem);
151 /** Used scheduler_ to get a random number b/w [0, n). If tls_sched is
152 * not set-up it falls back to std::rand() */
153 static int getRandNumber(int n);
155 /** Deterministic implemencation of getcpu */
156 static int getcpu(unsigned* cpu, unsigned* node, void* unused);
159 static FOLLY_TLS sem_t* tls_sem;
160 static FOLLY_TLS DeterministicSchedule* tls_sched;
161 static FOLLY_TLS unsigned tls_threadId;
163 std::function<int(int)> scheduler_;
164 std::vector<sem_t*> sems_;
165 std::unordered_set<std::thread::id> active_;
166 unsigned nextThreadId_;
168 sem_t* beforeThreadCreate();
169 void afterThreadCreate(sem_t*);
170 void beforeThreadExit();
174 * DeterministicAtomic<T> is a drop-in replacement std::atomic<T> that
175 * cooperates with DeterministicSchedule.
177 template <typename T>
178 struct DeterministicAtomic {
181 DeterministicAtomic() = default;
182 ~DeterministicAtomic() = default;
183 DeterministicAtomic(DeterministicAtomic<T> const&) = delete;
184 DeterministicAtomic<T>& operator=(DeterministicAtomic<T> const&) = delete;
186 constexpr /* implicit */ DeterministicAtomic(T v) noexcept : data(v) {}
188 bool is_lock_free() const noexcept { return data.is_lock_free(); }
190 bool compare_exchange_strong(
191 T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
192 DeterministicSchedule::beforeSharedAccess();
194 bool rv = data.compare_exchange_strong(v0, v1, mo);
195 FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_strong(" << std::hex
196 << orig << ", " << std::hex << v1 << ") -> "
197 << rv << "," << std::hex << v0);
198 DeterministicSchedule::afterSharedAccess();
202 bool compare_exchange_weak(
203 T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
204 DeterministicSchedule::beforeSharedAccess();
206 bool rv = data.compare_exchange_weak(v0, v1, mo);
207 FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_weak(" << std::hex << orig
208 << ", " << std::hex << v1 << ") -> " << rv
209 << "," << std::hex << v0);
210 DeterministicSchedule::afterSharedAccess();
214 T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
215 DeterministicSchedule::beforeSharedAccess();
216 T rv = data.exchange(v, mo);
217 FOLLY_TEST_DSCHED_VLOG(this << ".exchange(" << std::hex << v << ") -> "
219 DeterministicSchedule::afterSharedAccess();
223 /* implicit */ operator T() const noexcept {
224 DeterministicSchedule::beforeSharedAccess();
226 FOLLY_TEST_DSCHED_VLOG(this << "() -> " << std::hex << rv);
227 DeterministicSchedule::afterSharedAccess();
231 T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
232 DeterministicSchedule::beforeSharedAccess();
233 T rv = data.load(mo);
234 FOLLY_TEST_DSCHED_VLOG(this << ".load() -> " << std::hex << rv);
235 DeterministicSchedule::afterSharedAccess();
239 T operator=(T v) noexcept {
240 DeterministicSchedule::beforeSharedAccess();
242 FOLLY_TEST_DSCHED_VLOG(this << " = " << std::hex << v);
243 DeterministicSchedule::afterSharedAccess();
247 void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
248 DeterministicSchedule::beforeSharedAccess();
250 FOLLY_TEST_DSCHED_VLOG(this << ".store(" << std::hex << v << ")");
251 DeterministicSchedule::afterSharedAccess();
254 T operator++() noexcept {
255 DeterministicSchedule::beforeSharedAccess();
257 FOLLY_TEST_DSCHED_VLOG(this << " pre++ -> " << std::hex << rv);
258 DeterministicSchedule::afterSharedAccess();
262 T operator++(int /* postDummy */) noexcept {
263 DeterministicSchedule::beforeSharedAccess();
265 FOLLY_TEST_DSCHED_VLOG(this << " post++ -> " << std::hex << rv);
266 DeterministicSchedule::afterSharedAccess();
270 T operator--() noexcept {
271 DeterministicSchedule::beforeSharedAccess();
273 FOLLY_TEST_DSCHED_VLOG(this << " pre-- -> " << std::hex << rv);
274 DeterministicSchedule::afterSharedAccess();
278 T operator--(int /* postDummy */) noexcept {
279 DeterministicSchedule::beforeSharedAccess();
281 FOLLY_TEST_DSCHED_VLOG(this << " post-- -> " << std::hex << rv);
282 DeterministicSchedule::afterSharedAccess();
286 T operator+=(T v) noexcept {
287 DeterministicSchedule::beforeSharedAccess();
289 FOLLY_TEST_DSCHED_VLOG(this << " += " << std::hex << v << " -> " << std::hex
291 DeterministicSchedule::afterSharedAccess();
296 std::memory_order /* mo */ = std::memory_order_seq_cst) noexcept {
297 DeterministicSchedule::beforeSharedAccess();
300 FOLLY_TEST_DSCHED_VLOG(this << ".fetch_add(" << std::hex << v << ") -> "
302 DeterministicSchedule::afterSharedAccess();
306 T operator-=(T v) noexcept {
307 DeterministicSchedule::beforeSharedAccess();
309 FOLLY_TEST_DSCHED_VLOG(this << " -= " << std::hex << v << " -> " << std::hex
311 DeterministicSchedule::afterSharedAccess();
316 std::memory_order /* mo */ = std::memory_order_seq_cst) noexcept {
317 DeterministicSchedule::beforeSharedAccess();
320 FOLLY_TEST_DSCHED_VLOG(this << ".fetch_sub(" << std::hex << v << ") -> "
322 DeterministicSchedule::afterSharedAccess();
326 T operator&=(T v) noexcept {
327 DeterministicSchedule::beforeSharedAccess();
329 FOLLY_TEST_DSCHED_VLOG(this << " &= " << std::hex << v << " -> " << std::hex
331 DeterministicSchedule::afterSharedAccess();
336 std::memory_order /* mo */ = std::memory_order_seq_cst) noexcept {
337 DeterministicSchedule::beforeSharedAccess();
340 FOLLY_TEST_DSCHED_VLOG(this << ".fetch_and(" << std::hex << v << ") -> "
342 DeterministicSchedule::afterSharedAccess();
346 T operator|=(T v) noexcept {
347 DeterministicSchedule::beforeSharedAccess();
349 FOLLY_TEST_DSCHED_VLOG(this << " |= " << std::hex << v << " -> " << std::hex
351 DeterministicSchedule::afterSharedAccess();
356 std::memory_order /* mo */ = std::memory_order_seq_cst) noexcept {
357 DeterministicSchedule::beforeSharedAccess();
360 FOLLY_TEST_DSCHED_VLOG(this << ".fetch_or(" << std::hex << v << ") -> "
362 DeterministicSchedule::afterSharedAccess();
366 T operator^=(T v) noexcept {
367 DeterministicSchedule::beforeSharedAccess();
369 FOLLY_TEST_DSCHED_VLOG(this << " ^= " << std::hex << v << " -> " << std::hex
371 DeterministicSchedule::afterSharedAccess();
376 std::memory_order /* mo */ = std::memory_order_seq_cst) noexcept {
377 DeterministicSchedule::beforeSharedAccess();
380 FOLLY_TEST_DSCHED_VLOG(this << ".fetch_xor(" << std::hex << v << ") -> "
382 DeterministicSchedule::afterSharedAccess();
388 * DeterministicMutex is a drop-in replacement of std::mutex that
389 * cooperates with DeterministicSchedule.
391 struct DeterministicMutex {
394 DeterministicMutex() = default;
395 ~DeterministicMutex() = default;
396 DeterministicMutex(DeterministicMutex const&) = delete;
397 DeterministicMutex& operator=(DeterministicMutex const&) = delete;
400 FOLLY_TEST_DSCHED_VLOG(this << ".lock()");
401 while (!try_lock()) {
402 // Not calling m.lock() in order to avoid deadlock when the
403 // mutex m is held by another thread. The deadlock would be
404 // between the call to m.lock() and the lock holder's wait on
405 // its own tls_sem scheduling semaphore.
410 DeterministicSchedule::beforeSharedAccess();
411 bool rv = m.try_lock();
412 FOLLY_TEST_DSCHED_VLOG(this << ".try_lock() -> " << rv);
413 DeterministicSchedule::afterSharedAccess();
418 FOLLY_TEST_DSCHED_VLOG(this << ".unlock()");
423 } // namespace folly::test
425 /* Specialization declarations */
431 int Futex<test::DeterministicAtomic>::futexWake(int count, uint32_t wakeMask);
434 FutexResult Futex<test::DeterministicAtomic>::futexWaitImpl(
436 std::chrono::time_point<std::chrono::system_clock>* absSystemTime,
437 std::chrono::time_point<std::chrono::steady_clock>* absSteadyTime,
441 Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc();
443 } // namespace folly::detail