Fix folly/ThreadLocal with clang after PthreadKeyUnregister change
[folly.git] / folly / test / DeterministicSchedule.h
1 /*
2  * Copyright 2015 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
17 #pragma once
18
19 #include <atomic>
20 #include <functional>
21 #include <thread>
22 #include <unordered_set>
23 #include <vector>
24 #include <boost/noncopyable.hpp>
25 #include <semaphore.h>
26 #include <errno.h>
27 #include <assert.h>
28 #include <glog/logging.h>
29
30 #include <folly/ScopeGuard.h>
31 #include <folly/detail/CacheLocality.h>
32 #include <folly/detail/Futex.h>
33
34 namespace folly {
35 namespace test {
36
37 // This is ugly, but better perf for DeterministicAtomic translates
38 // directly to more states explored and tested
39 #define FOLLY_TEST_DSCHED_VLOG(msg...)                                  \
40   do {                                                                  \
41     if (false) {                                                        \
42       VLOG(2) << std::hex << std::this_thread::get_id() << ": " << msg; \
43     }                                                                   \
44   } while (false)
45
46 /**
47  * DeterministicSchedule coordinates the inter-thread communication of a
48  * set of threads under test, so that despite concurrency the execution is
49  * the same every time.  It works by stashing a reference to the schedule
50  * in a thread-local variable, then blocking all but one thread at a time.
51  *
52  * In order for DeterministicSchedule to work, it needs to intercept
53  * all inter-thread communication.  To do this you should use
54  * DeterministicAtomic<T> instead of std::atomic<T>, create threads
55  * using DeterministicSchedule::thread() instead of the std::thread
56  * constructor, DeterministicSchedule::join(thr) instead of thr.join(),
57  * and access semaphores via the helper functions in DeterministicSchedule.
58  * Locks are not yet supported, although they would be easy to add with
59  * the same strategy as the mapping of sem_wait.
60  *
61  * The actual schedule is defined by a function from n -> [0,n). At
62  * each step, the function will be given the number of active threads
63  * (n), and it returns the index of the thread that should be run next.
64  * Invocations of the scheduler function will be serialized, but will
65  * occur from multiple threads.  A good starting schedule is uniform(0).
66  */
67 class DeterministicSchedule : boost::noncopyable {
68  public:
69   /**
70    * Arranges for the current thread (and all threads created by
71    * DeterministicSchedule::thread on a thread participating in this
72    * schedule) to participate in a deterministic schedule.
73    */
74   explicit DeterministicSchedule(const std::function<int(int)>& scheduler);
75
76   /** Completes the schedule. */
77   ~DeterministicSchedule();
78
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<int(int)> uniform(long seed);
87
88   /**
89    * Returns a scheduling function that chooses a subset of the active
90    * threads and randomly chooses a member of the subset as the next
91    * runnable thread.  The subset is chosen with size n, and the choice
92    * is made every m steps.
93    */
94   static std::function<int(int)> uniformSubset(long seed,
95                                                int n = 2,
96                                                int m = 64);
97
98   /** Obtains permission for the current thread to perform inter-thread
99    *  communication. */
100   static void beforeSharedAccess();
101
102   /** Releases permission for the current thread to perform inter-thread
103    *  communication. */
104   static void afterSharedAccess();
105
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([=](Args... a) {
114       if (sched) {
115         sched->afterThreadCreate(sem);
116         beforeSharedAccess();
117         FOLLY_TEST_DSCHED_VLOG("running");
118         afterSharedAccess();
119       }
120       SCOPE_EXIT {
121         if (sched) {
122           sched->beforeThreadExit();
123         }
124       };
125       func(a...);
126     }, args...);
127     if (sched) {
128       beforeSharedAccess();
129       sched->active_.insert(child.get_id());
130       FOLLY_TEST_DSCHED_VLOG("forked " << std::hex << child.get_id());
131       afterSharedAccess();
132     }
133     return child;
134   }
135
136   /** Calls child.join() as part of a deterministic schedule. */
137   static void join(std::thread& child);
138
139   /** Calls sem_post(sem) as part of a deterministic schedule. */
140   static void post(sem_t* sem);
141
142   /** Calls sem_trywait(sem) as part of a deterministic schedule, returning
143    *  true on success and false on transient failure. */
144   static bool tryWait(sem_t* sem);
145
146   /** Calls sem_wait(sem) as part of a deterministic schedule. */
147   static void wait(sem_t* sem);
148
149   /** Used scheduler_ to get a random number b/w [0, n). If tls_sched is
150    *  not set-up it falls back to std::rand() */
151   static int getRandNumber(int n);
152
153   /** Deterministic implemencation of getcpu */
154   static int getcpu(unsigned* cpu, unsigned* node, void* unused);
155
156  private:
157   static FOLLY_TLS sem_t* tls_sem;
158   static FOLLY_TLS DeterministicSchedule* tls_sched;
159   static FOLLY_TLS unsigned tls_threadId;
160
161   std::function<int(int)> scheduler_;
162   std::vector<sem_t*> sems_;
163   std::unordered_set<std::thread::id> active_;
164   unsigned nextThreadId_;
165
166   sem_t* beforeThreadCreate();
167   void afterThreadCreate(sem_t*);
168   void beforeThreadExit();
169 };
170
171 /**
172  * DeterministicAtomic<T> is a drop-in replacement std::atomic<T> that
173  * cooperates with DeterministicSchedule.
174  */
175 template <typename T>
176 struct DeterministicAtomic {
177   std::atomic<T> data;
178
179   DeterministicAtomic() = default;
180   ~DeterministicAtomic() = default;
181   DeterministicAtomic(DeterministicAtomic<T> const&) = delete;
182   DeterministicAtomic<T>& operator=(DeterministicAtomic<T> const&) = delete;
183
184   constexpr /* implicit */ DeterministicAtomic(T v) noexcept : data(v) {}
185
186   bool is_lock_free() const noexcept { return data.is_lock_free(); }
187
188   bool compare_exchange_strong(
189       T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
190     DeterministicSchedule::beforeSharedAccess();
191     auto orig = v0;
192     bool rv = data.compare_exchange_strong(v0, v1, mo);
193     FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_strong(" << std::hex
194                                 << orig << ", " << std::hex << v1 << ") -> "
195                                 << rv << "," << std::hex << v0);
196     DeterministicSchedule::afterSharedAccess();
197     return rv;
198   }
199
200   bool compare_exchange_weak(
201       T& v0, T v1, std::memory_order mo = std::memory_order_seq_cst) noexcept {
202     DeterministicSchedule::beforeSharedAccess();
203     auto orig = v0;
204     bool rv = data.compare_exchange_weak(v0, v1, mo);
205     FOLLY_TEST_DSCHED_VLOG(this << ".compare_exchange_weak(" << std::hex << orig
206                                 << ", " << std::hex << v1 << ") -> " << rv
207                                 << "," << std::hex << v0);
208     DeterministicSchedule::afterSharedAccess();
209     return rv;
210   }
211
212   T exchange(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
213     DeterministicSchedule::beforeSharedAccess();
214     T rv = data.exchange(v, mo);
215     FOLLY_TEST_DSCHED_VLOG(this << ".exchange(" << std::hex << v << ") -> "
216                                 << std::hex << rv);
217     DeterministicSchedule::afterSharedAccess();
218     return rv;
219   }
220
221   /* implicit */ operator T() const noexcept {
222     DeterministicSchedule::beforeSharedAccess();
223     T rv = data;
224     FOLLY_TEST_DSCHED_VLOG(this << "() -> " << std::hex << rv);
225     DeterministicSchedule::afterSharedAccess();
226     return rv;
227   }
228
229   T load(std::memory_order mo = std::memory_order_seq_cst) const noexcept {
230     DeterministicSchedule::beforeSharedAccess();
231     T rv = data.load(mo);
232     FOLLY_TEST_DSCHED_VLOG(this << ".load() -> " << std::hex << rv);
233     DeterministicSchedule::afterSharedAccess();
234     return rv;
235   }
236
237   T operator=(T v) noexcept {
238     DeterministicSchedule::beforeSharedAccess();
239     T rv = (data = v);
240     FOLLY_TEST_DSCHED_VLOG(this << " = " << std::hex << v);
241     DeterministicSchedule::afterSharedAccess();
242     return rv;
243   }
244
245   void store(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
246     DeterministicSchedule::beforeSharedAccess();
247     data.store(v, mo);
248     FOLLY_TEST_DSCHED_VLOG(this << ".store(" << std::hex << v << ")");
249     DeterministicSchedule::afterSharedAccess();
250   }
251
252   T operator++() noexcept {
253     DeterministicSchedule::beforeSharedAccess();
254     T rv = ++data;
255     FOLLY_TEST_DSCHED_VLOG(this << " pre++ -> " << std::hex << rv);
256     DeterministicSchedule::afterSharedAccess();
257     return rv;
258   }
259
260   T operator++(int postDummy) noexcept {
261     DeterministicSchedule::beforeSharedAccess();
262     T rv = data++;
263     FOLLY_TEST_DSCHED_VLOG(this << " post++ -> " << std::hex << rv);
264     DeterministicSchedule::afterSharedAccess();
265     return rv;
266   }
267
268   T operator--() noexcept {
269     DeterministicSchedule::beforeSharedAccess();
270     T rv = --data;
271     FOLLY_TEST_DSCHED_VLOG(this << " pre-- -> " << std::hex << rv);
272     DeterministicSchedule::afterSharedAccess();
273     return rv;
274   }
275
276   T operator--(int postDummy) noexcept {
277     DeterministicSchedule::beforeSharedAccess();
278     T rv = data--;
279     FOLLY_TEST_DSCHED_VLOG(this << " post-- -> " << std::hex << rv);
280     DeterministicSchedule::afterSharedAccess();
281     return rv;
282   }
283
284   T operator+=(T v) noexcept {
285     DeterministicSchedule::beforeSharedAccess();
286     T rv = (data += v);
287     FOLLY_TEST_DSCHED_VLOG(this << " += " << std::hex << v << " -> " << std::hex
288                                 << rv);
289     DeterministicSchedule::afterSharedAccess();
290     return rv;
291   }
292
293   T fetch_add(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
294     DeterministicSchedule::beforeSharedAccess();
295     T rv = data;
296     data += v;
297     FOLLY_TEST_DSCHED_VLOG(this << ".fetch_add(" << std::hex << v << ") -> "
298                                 << std::hex << rv);
299     DeterministicSchedule::afterSharedAccess();
300     return rv;
301   }
302
303   T operator-=(T v) noexcept {
304     DeterministicSchedule::beforeSharedAccess();
305     T rv = (data -= v);
306     FOLLY_TEST_DSCHED_VLOG(this << " -= " << std::hex << v << " -> " << std::hex
307                                 << rv);
308     DeterministicSchedule::afterSharedAccess();
309     return rv;
310   }
311
312   T fetch_sub(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
313     DeterministicSchedule::beforeSharedAccess();
314     T rv = data;
315     data -= v;
316     FOLLY_TEST_DSCHED_VLOG(this << ".fetch_sub(" << std::hex << v << ") -> "
317                                 << std::hex << rv);
318     DeterministicSchedule::afterSharedAccess();
319     return rv;
320   }
321
322   T operator&=(T v) noexcept {
323     DeterministicSchedule::beforeSharedAccess();
324     T rv = (data &= v);
325     FOLLY_TEST_DSCHED_VLOG(this << " &= " << std::hex << v << " -> " << std::hex
326                                 << rv);
327     DeterministicSchedule::afterSharedAccess();
328     return rv;
329   }
330
331   T fetch_and(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
332     DeterministicSchedule::beforeSharedAccess();
333     T rv = data;
334     data &= v;
335     FOLLY_TEST_DSCHED_VLOG(this << ".fetch_and(" << std::hex << v << ") -> "
336                                 << std::hex << rv);
337     DeterministicSchedule::afterSharedAccess();
338     return rv;
339   }
340
341   T operator|=(T v) noexcept {
342     DeterministicSchedule::beforeSharedAccess();
343     T rv = (data |= v);
344     FOLLY_TEST_DSCHED_VLOG(this << " |= " << std::hex << v << " -> " << std::hex
345                                 << rv);
346     DeterministicSchedule::afterSharedAccess();
347     return rv;
348   }
349
350   T fetch_or(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
351     DeterministicSchedule::beforeSharedAccess();
352     T rv = data;
353     data |= v;
354     FOLLY_TEST_DSCHED_VLOG(this << ".fetch_or(" << std::hex << v << ") -> "
355                                 << std::hex << rv);
356     DeterministicSchedule::afterSharedAccess();
357     return rv;
358   }
359
360   T operator^=(T v) noexcept {
361     DeterministicSchedule::beforeSharedAccess();
362     T rv = (data ^= v);
363     FOLLY_TEST_DSCHED_VLOG(this << " ^= " << std::hex << v << " -> " << std::hex
364                                 << rv);
365     DeterministicSchedule::afterSharedAccess();
366     return rv;
367   }
368
369   T fetch_xor(T v, std::memory_order mo = std::memory_order_seq_cst) noexcept {
370     DeterministicSchedule::beforeSharedAccess();
371     T rv = data;
372     data ^= v;
373     FOLLY_TEST_DSCHED_VLOG(this << ".fetch_xor(" << std::hex << v << ") -> "
374                                 << std::hex << rv);
375     DeterministicSchedule::afterSharedAccess();
376     return rv;
377   }
378 };
379 }
380 } // namespace folly::test
381
382 /* Specialization declarations */
383
384 namespace folly {
385 namespace detail {
386
387 template <>
388 int Futex<test::DeterministicAtomic>::futexWake(int count, uint32_t wakeMask);
389
390 template <>
391 FutexResult Futex<test::DeterministicAtomic>::futexWaitImpl(
392     uint32_t expected,
393     std::chrono::time_point<std::chrono::system_clock>* absSystemTime,
394     std::chrono::time_point<std::chrono::steady_clock>* absSteadyTime,
395     uint32_t waitMask);
396
397 template <>
398 Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc(
399     size_t numStripes);
400 }
401 } // namespace folly::detail