DeterministicSchedule.h 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /*
  2. * Copyright 2013-present Facebook, Inc.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. #pragma once
  17. #include <assert.h>
  18. #include <boost/noncopyable.hpp>
  19. #include <errno.h>
  20. #include <glog/logging.h>
  21. #include <atomic>
  22. #include <functional>
  23. #include <mutex>
  24. #include <queue>
  25. #include <thread>
  26. #include <unordered_set>
  27. #include <vector>
  28. #include <folly/ScopeGuard.h>
  29. #include <folly/concurrency/CacheLocality.h>
  30. #include <folly/detail/Futex.h>
  31. #include <folly/portability/Semaphore.h>
  32. #include <folly/synchronization/detail/AtomicUtils.h>
  33. namespace folly {
  34. namespace test {
  35. // This is ugly, but better perf for DeterministicAtomic translates
  36. // directly to more states explored and tested
  37. #define FOLLY_TEST_DSCHED_VLOG(...) \
  38. do { \
  39. if (false) { \
  40. VLOG(2) << std::hex << std::this_thread::get_id() << ": " \
  41. << __VA_ARGS__; \
  42. } \
  43. } while (false)
  44. /* signatures of user-defined auxiliary functions */
  45. using AuxAct = std::function<void(bool)>;
  46. using AuxChk = std::function<void(uint64_t)>;
  47. /**
  48. * DeterministicSchedule coordinates the inter-thread communication of a
  49. * set of threads under test, so that despite concurrency the execution is
  50. * the same every time. It works by stashing a reference to the schedule
  51. * in a thread-local variable, then blocking all but one thread at a time.
  52. *
  53. * In order for DeterministicSchedule to work, it needs to intercept
  54. * all inter-thread communication. To do this you should use
  55. * DeterministicAtomic<T> instead of std::atomic<T>, create threads
  56. * using DeterministicSchedule::thread() instead of the std::thread
  57. * constructor, DeterministicSchedule::join(thr) instead of thr.join(),
  58. * and access semaphores via the helper functions in DeterministicSchedule.
  59. * Locks are not yet supported, although they would be easy to add with
  60. * the same strategy as the mapping of sem_wait.
  61. *
  62. * The actual schedule is defined by a function from n -> [0,n). At
  63. * each step, the function will be given the number of active threads
  64. * (n), and it returns the index of the thread that should be run next.
  65. * Invocations of the scheduler function will be serialized, but will
  66. * occur from multiple threads. A good starting schedule is uniform(0).
  67. */
  68. class DeterministicSchedule : boost::noncopyable {
  69. public:
  70. /**
  71. * Arranges for the current thread (and all threads created by
  72. * DeterministicSchedule::thread on a thread participating in this
  73. * schedule) to participate in a deterministic schedule.
  74. */
  75. explicit DeterministicSchedule(
  76. const std::function<size_t(size_t)>& scheduler);
  77. /** Completes the schedule. */
  78. ~DeterministicSchedule();
  79. /**
  80. * Returns a scheduling function that randomly chooses one of the
  81. * runnable threads at each step, with no history. This implements
  82. * a schedule that is equivalent to one in which the steps between
  83. * inter-thread communication are random variables following a poisson
  84. * distribution.
  85. */
  86. static std::function<size_t(size_t)> uniform(uint64_t seed);
  87. /**
  88. * Returns a scheduling function that chooses a subset of the active
  89. * threads and randomly chooses a member of the subset as the next
  90. * runnable thread. The subset is chosen with size n, and the choice
  91. * is made every m steps.
  92. */
  93. static std::function<size_t(size_t)>
  94. uniformSubset(uint64_t seed, size_t n = 2, size_t m = 64);
  95. /** Obtains permission for the current thread to perform inter-thread
  96. * communication. */
  97. static void beforeSharedAccess();
  98. /** Releases permission for the current thread to perform inter-thread
  99. * communication. */
  100. static void afterSharedAccess();
  101. /** Calls a user-defined auxiliary function if any, and releases
  102. * permission for the current thread to perform inter-thread
  103. * communication. The bool parameter indicates the success of the
  104. * shared access (if conditional, true otherwise). */
  105. static void afterSharedAccess(bool success);
  106. /** Launches a thread that will participate in the same deterministic
  107. * schedule as the current thread. */
  108. template <typename Func, typename... Args>
  109. static inline std::thread thread(Func&& func, Args&&... args) {
  110. // TODO: maybe future versions of gcc will allow forwarding to thread
  111. auto sched = tls_sched;
  112. auto sem = sched ? sched->beforeThreadCreate() : nullptr;
  113. auto child = std::thread(
  114. [=](Args... a) {
  115. if (sched) {
  116. sched->afterThreadCreate(sem);
  117. beforeSharedAccess();
  118. FOLLY_TEST_DSCHED_VLOG("running");
  119. afterSharedAccess();
  120. }
  121. SCOPE_EXIT {
  122. if (sched) {
  123. sched->beforeThreadExit();
  124. }
  125. };
  126. func(a...);
  127. },
  128. args...);
  129. if (sched) {
  130. beforeSharedAccess();
  131. sched->active_.insert(child.get_id());
  132. FOLLY_TEST_DSCHED_VLOG("forked " << std::hex << child.get_id());
  133. afterSharedAccess();
  134. }
  135. return child;
  136. }
  137. /** Calls child.join() as part of a deterministic schedule. */
  138. static void join(std::thread& child);
  139. /** Calls sem_post(sem) as part of a deterministic schedule. */
  140. static void post(sem_t* sem);
  141. /** Calls sem_trywait(sem) as part of a deterministic schedule, returning
  142. * true on success and false on transient failure. */
  143. static bool tryWait(sem_t* sem);
  144. /** Calls sem_wait(sem) as part of a deterministic schedule. */
  145. static void wait(sem_t* sem);
  146. /** Used scheduler_ to get a random number b/w [0, n). If tls_sched is
  147. * not set-up it falls back to std::rand() */
  148. static size_t getRandNumber(size_t n);
  149. /** Deterministic implemencation of getcpu */
  150. static int getcpu(unsigned* cpu, unsigned* node, void* unused);
  151. /** Sets up a thread-specific function for call immediately after
  152. * the next shared access by the thread for managing auxiliary
  153. * data. The function takes a bool parameter that indicates the
  154. * success of the shared access (if it is conditional, true
  155. * otherwise). The function is cleared after one use. */
  156. static void setAuxAct(AuxAct& aux);
  157. /** Sets up a function to be called after every subsequent shared
  158. * access (until clearAuxChk() is called) for checking global
  159. * invariants and logging. The function takes a uint64_t parameter
  160. * that indicates the number of shared accesses so far. */
  161. static void setAuxChk(AuxChk& aux);
  162. /** Clears the function set by setAuxChk */
  163. static void clearAuxChk();
  164. /** Remove the current thread's semaphore from sems_ */
  165. static sem_t* descheduleCurrentThread();
  166. /** Add sem back into sems_ */
  167. static void reschedule(sem_t* sem);
  168. private:
  169. static FOLLY_TLS sem_t* tls_sem;
  170. static FOLLY_TLS DeterministicSchedule* tls_sched;
  171. static FOLLY_TLS unsigned tls_threadId;
  172. static thread_local AuxAct tls_aux_act;
  173. static AuxChk aux_chk;
  174. std::function<size_t(size_t)> scheduler_;
  175. std::vector<sem_t*> sems_;
  176. std::unordered_set<std::thread::id> active_;
  177. std::unordered_map<std::thread::id, sem_t*> joins_;
  178. unsigned nextThreadId_;
  179. /* step_ keeps count of shared accesses that correspond to user
  180. * synchronization steps (atomic accesses for now).
  181. * The reason for keeping track of this here and not just with
  182. * auxiliary data is to provide users with warning signs (e.g.,
  183. * skipped steps) if they inadvertently forget to set up aux
  184. * functions for some shared accesses. */
  185. uint64_t step_;
  186. sem_t* beforeThreadCreate();
  187. void afterThreadCreate(sem_t*);
  188. void beforeThreadExit();
  189. /** Calls user-defined auxiliary function (if any) */
  190. void callAux(bool);
  191. };
  192. /**
  193. * DeterministicAtomic<T> is a drop-in replacement std::atomic<T> that
  194. * cooperates with DeterministicSchedule.
  195. */
  196. template <
  197. typename T,
  198. typename Schedule = DeterministicSchedule,
  199. template <typename> class Atom = std::atomic>
  200. struct DeterministicAtomicImpl {
  201. DeterministicAtomicImpl() = default;
  202. ~DeterministicAtomicImpl() = default;
  203. DeterministicAtomicImpl(DeterministicAtomicImpl<T> const&) = delete;
  204. DeterministicAtomicImpl<T>& operator=(DeterministicAtomicImpl<T> const&) =
  205. delete;
  206. constexpr /* implicit */ DeterministicAtomicImpl(T v) noexcept : data_(v) {}
  207. bool is_lock_free() const noexcept {
  208. return data_.is_lock_free();
  209. }
  210. bool compare_exchange_strong(
  211. T& v0,
  212. T v1,
  213. std::memory_order mo = std::memory_order_seq_cst) noexcept {
  214. return compare_exchange_strong(
  215. v0, v1, mo, ::folly::detail::default_failure_memory_order(mo));
  216. }
  217. bool compare_exchange_strong(
  218. T& v0,
  219. T v1,
  220. std::memory_order success,
  221. std::memory_order failure) noexcept {
  222. Schedule::beforeSharedAccess();
  223. auto orig = v0;
  224. bool rv = data_.compare_exchange_strong(v0, v1, success, failure);
  225. FOLLY_TEST_DSCHED_VLOG(
  226. this << ".compare_exchange_strong(" << std::hex << orig << ", "
  227. << std::hex << v1 << ") -> " << rv << "," << std::hex << v0);
  228. Schedule::afterSharedAccess(rv);
  229. return rv;
  230. }
  231. bool compare_exchange_weak(
  232. T& v0,
  233. T v1,
  234. std::memory_order mo = std::memory_order_seq_cst) noexcept {
  235. return compare_exchange_weak(
  236. v0, v1, mo, ::folly::detail::default_failure_memory_order(mo));
  237. }
  238. bool compare_exchange_weak(
  239. T& v0,
  240. T v1,
  241. std::memory_order success,
  242. std::memory_order failure) noexcept {
  243. Schedule::beforeSharedAccess();
  244. auto orig = v0;
  245. bool rv = data_.compare_exchange_weak(v0, v1, success, failure);
  246. FOLLY_TEST_DSCHED_VLOG(
  247. this << ".compare_exchange_weak(" << std::hex << orig << ", "
  248. << std::hex << v1 << ") -> " << rv << "," << std::hex << v0);
  249. Schedule::afterSharedAccess(rv);
  250. return rv;
  251. }
  252. T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  253. Schedule::beforeSharedAccess();
  254. T rv = data_.exchange(v, mo);
  255. FOLLY_TEST_DSCHED_VLOG(
  256. this << ".exchange(" << std::hex << v << ") -> " << std::hex << rv);
  257. Schedule::afterSharedAccess(true);
  258. return rv;
  259. }
  260. /* implicit */ operator T() const noexcept {
  261. Schedule::beforeSharedAccess();
  262. T rv = data_.operator T();
  263. FOLLY_TEST_DSCHED_VLOG(this << "() -> " << std::hex << rv);
  264. Schedule::afterSharedAccess(true);
  265. return rv;
  266. }
  267. T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
  268. Schedule::beforeSharedAccess();
  269. T rv = data_.load(mo);
  270. FOLLY_TEST_DSCHED_VLOG(this << ".load() -> " << std::hex << rv);
  271. Schedule::afterSharedAccess(true);
  272. return rv;
  273. }
  274. T operator=(T v) noexcept {
  275. Schedule::beforeSharedAccess();
  276. T rv = (data_ = v);
  277. FOLLY_TEST_DSCHED_VLOG(this << " = " << std::hex << v);
  278. Schedule::afterSharedAccess(true);
  279. return rv;
  280. }
  281. void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  282. Schedule::beforeSharedAccess();
  283. data_.store(v, mo);
  284. FOLLY_TEST_DSCHED_VLOG(this << ".store(" << std::hex << v << ")");
  285. Schedule::afterSharedAccess(true);
  286. }
  287. T operator++() noexcept {
  288. Schedule::beforeSharedAccess();
  289. T rv = ++data_;
  290. FOLLY_TEST_DSCHED_VLOG(this << " pre++ -> " << std::hex << rv);
  291. Schedule::afterSharedAccess(true);
  292. return rv;
  293. }
  294. T operator++(int /* postDummy */) noexcept {
  295. Schedule::beforeSharedAccess();
  296. T rv = data_++;
  297. FOLLY_TEST_DSCHED_VLOG(this << " post++ -> " << std::hex << rv);
  298. Schedule::afterSharedAccess(true);
  299. return rv;
  300. }
  301. T operator--() noexcept {
  302. Schedule::beforeSharedAccess();
  303. T rv = --data_;
  304. FOLLY_TEST_DSCHED_VLOG(this << " pre-- -> " << std::hex << rv);
  305. Schedule::afterSharedAccess(true);
  306. return rv;
  307. }
  308. T operator--(int /* postDummy */) noexcept {
  309. Schedule::beforeSharedAccess();
  310. T rv = data_--;
  311. FOLLY_TEST_DSCHED_VLOG(this << " post-- -> " << std::hex << rv);
  312. Schedule::afterSharedAccess(true);
  313. return rv;
  314. }
  315. T operator+=(T v) noexcept {
  316. Schedule::beforeSharedAccess();
  317. T rv = (data_ += v);
  318. FOLLY_TEST_DSCHED_VLOG(
  319. this << " += " << std::hex << v << " -> " << std::hex << rv);
  320. Schedule::afterSharedAccess(true);
  321. return rv;
  322. }
  323. T fetch_add(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  324. Schedule::beforeSharedAccess();
  325. T rv = data_.fetch_add(v, mo);
  326. FOLLY_TEST_DSCHED_VLOG(
  327. this << ".fetch_add(" << std::hex << v << ") -> " << std::hex << rv);
  328. Schedule::afterSharedAccess(true);
  329. return rv;
  330. }
  331. T operator-=(T v) noexcept {
  332. Schedule::beforeSharedAccess();
  333. T rv = (data_ -= v);
  334. FOLLY_TEST_DSCHED_VLOG(
  335. this << " -= " << std::hex << v << " -> " << std::hex << rv);
  336. Schedule::afterSharedAccess(true);
  337. return rv;
  338. }
  339. T fetch_sub(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  340. Schedule::beforeSharedAccess();
  341. T rv = data_.fetch_sub(v, mo);
  342. FOLLY_TEST_DSCHED_VLOG(
  343. this << ".fetch_sub(" << std::hex << v << ") -> " << std::hex << rv);
  344. Schedule::afterSharedAccess(true);
  345. return rv;
  346. }
  347. T operator&=(T v) noexcept {
  348. Schedule::beforeSharedAccess();
  349. T rv = (data_ &= v);
  350. FOLLY_TEST_DSCHED_VLOG(
  351. this << " &= " << std::hex << v << " -> " << std::hex << rv);
  352. Schedule::afterSharedAccess(true);
  353. return rv;
  354. }
  355. T fetch_and(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  356. Schedule::beforeSharedAccess();
  357. T rv = data_.fetch_and(v, mo);
  358. FOLLY_TEST_DSCHED_VLOG(
  359. this << ".fetch_and(" << std::hex << v << ") -> " << std::hex << rv);
  360. Schedule::afterSharedAccess(true);
  361. return rv;
  362. }
  363. T operator|=(T v) noexcept {
  364. Schedule::beforeSharedAccess();
  365. T rv = (data_ |= v);
  366. FOLLY_TEST_DSCHED_VLOG(
  367. this << " |= " << std::hex << v << " -> " << std::hex << rv);
  368. Schedule::afterSharedAccess(true);
  369. return rv;
  370. }
  371. T fetch_or(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  372. Schedule::beforeSharedAccess();
  373. T rv = data_.fetch_or(v, mo);
  374. FOLLY_TEST_DSCHED_VLOG(
  375. this << ".fetch_or(" << std::hex << v << ") -> " << std::hex << rv);
  376. Schedule::afterSharedAccess(true);
  377. return rv;
  378. }
  379. T operator^=(T v) noexcept {
  380. Schedule::beforeSharedAccess();
  381. T rv = (data_ ^= v);
  382. FOLLY_TEST_DSCHED_VLOG(
  383. this << " ^= " << std::hex << v << " -> " << std::hex << rv);
  384. Schedule::afterSharedAccess(true);
  385. return rv;
  386. }
  387. T fetch_xor(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
  388. Schedule::beforeSharedAccess();
  389. T rv = data_.fetch_xor(v, mo);
  390. FOLLY_TEST_DSCHED_VLOG(
  391. this << ".fetch_xor(" << std::hex << v << ") -> " << std::hex << rv);
  392. Schedule::afterSharedAccess(true);
  393. return rv;
  394. }
  395. /** Read the value of the atomic variable without context switching */
  396. T load_direct() const noexcept {
  397. return data_.load(std::memory_order_relaxed);
  398. }
  399. private:
  400. Atom<T> data_;
  401. };
  402. template <typename T>
  403. using DeterministicAtomic = DeterministicAtomicImpl<T, DeterministicSchedule>;
  404. /* Futex extensions for DeterministicSchedule based Futexes */
  405. int futexWakeImpl(
  406. const detail::Futex<test::DeterministicAtomic>* futex,
  407. int count,
  408. uint32_t wakeMask);
  409. detail::FutexResult futexWaitImpl(
  410. const detail::Futex<test::DeterministicAtomic>* futex,
  411. uint32_t expected,
  412. std::chrono::system_clock::time_point const* absSystemTime,
  413. std::chrono::steady_clock::time_point const* absSteadyTime,
  414. uint32_t waitMask);
  415. /**
  416. * Implementations of the atomic_wait API for DeterministicAtomic, these are
  417. * no-ops here. Which for a correct implementation should not make a
  418. * difference because threads are required to have atomic operations around
  419. * waits and wakes
  420. */
  421. template <typename Integer>
  422. void atomic_wait(const DeterministicAtomic<Integer>*, Integer) {}
  423. template <typename Integer, typename Clock, typename Duration>
  424. std::cv_status atomic_wait_until(
  425. const DeterministicAtomic<Integer>*,
  426. Integer,
  427. const std::chrono::time_point<Clock, Duration>&) {
  428. return std::cv_status::no_timeout;
  429. }
  430. template <typename Integer>
  431. void atomic_notify_one(const DeterministicAtomic<Integer>*) {}
  432. template <typename Integer>
  433. void atomic_notify_all(const DeterministicAtomic<Integer>*) {}
  434. /**
  435. * DeterministicMutex is a drop-in replacement of std::mutex that
  436. * cooperates with DeterministicSchedule.
  437. */
  438. struct DeterministicMutex {
  439. std::mutex m;
  440. std::queue<sem_t*> waiters_;
  441. DeterministicMutex() = default;
  442. ~DeterministicMutex() = default;
  443. DeterministicMutex(DeterministicMutex const&) = delete;
  444. DeterministicMutex& operator=(DeterministicMutex const&) = delete;
  445. void lock() {
  446. FOLLY_TEST_DSCHED_VLOG(this << ".lock()");
  447. DeterministicSchedule::beforeSharedAccess();
  448. while (!m.try_lock()) {
  449. sem_t* sem = DeterministicSchedule::descheduleCurrentThread();
  450. if (sem) {
  451. waiters_.push(sem);
  452. }
  453. DeterministicSchedule::afterSharedAccess();
  454. // Wait to be scheduled by unlock
  455. DeterministicSchedule::beforeSharedAccess();
  456. }
  457. DeterministicSchedule::afterSharedAccess();
  458. }
  459. bool try_lock() {
  460. DeterministicSchedule::beforeSharedAccess();
  461. bool rv = m.try_lock();
  462. FOLLY_TEST_DSCHED_VLOG(this << ".try_lock() -> " << rv);
  463. DeterministicSchedule::afterSharedAccess();
  464. return rv;
  465. }
  466. void unlock() {
  467. FOLLY_TEST_DSCHED_VLOG(this << ".unlock()");
  468. m.unlock();
  469. DeterministicSchedule::beforeSharedAccess();
  470. if (!waiters_.empty()) {
  471. sem_t* sem = waiters_.front();
  472. DeterministicSchedule::reschedule(sem);
  473. waiters_.pop();
  474. }
  475. DeterministicSchedule::afterSharedAccess();
  476. }
  477. };
  478. } // namespace test
  479. template <>
  480. Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc();
  481. } // namespace folly