fix compiler warnings from gcc-4.9 + -Wunused-variable
[folly.git] / folly / test / DeterministicSchedule.cpp
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 #include <folly/test/DeterministicSchedule.h>
18 #include <algorithm>
19 #include <list>
20 #include <mutex>
21 #include <random>
22 #include <utility>
23 #include <unordered_map>
24 #include <assert.h>
25
26 namespace folly {
27 namespace test {
28
29 FOLLY_TLS sem_t* DeterministicSchedule::tls_sem;
30 FOLLY_TLS DeterministicSchedule* DeterministicSchedule::tls_sched;
31 FOLLY_TLS unsigned DeterministicSchedule::tls_threadId;
32
33 // access is protected by futexLock
34 static std::unordered_map<detail::Futex<DeterministicAtomic>*,
35                           std::list<std::pair<uint32_t, bool*>>> futexQueues;
36
37 static std::mutex futexLock;
38
39 DeterministicSchedule::DeterministicSchedule(
40     const std::function<int(int)>& scheduler)
41     : scheduler_(scheduler), nextThreadId_(1) {
42   assert(tls_sem == nullptr);
43   assert(tls_sched == nullptr);
44
45   tls_sem = new sem_t;
46   sem_init(tls_sem, 0, 1);
47   sems_.push_back(tls_sem);
48
49   tls_sched = this;
50 }
51
52 DeterministicSchedule::~DeterministicSchedule() {
53   assert(tls_sched == this);
54   assert(sems_.size() == 1);
55   assert(sems_[0] == tls_sem);
56   beforeThreadExit();
57 }
58
59 std::function<int(int)> DeterministicSchedule::uniform(long seed) {
60   auto rand = std::make_shared<std::ranlux48>(seed);
61   return [rand](size_t numActive) {
62     auto dist = std::uniform_int_distribution<int>(0, numActive - 1);
63     return dist(*rand);
64   };
65 }
66
67 struct UniformSubset {
68   UniformSubset(long seed, int subsetSize, int stepsBetweenSelect)
69       : uniform_(DeterministicSchedule::uniform(seed)),
70         subsetSize_(subsetSize),
71         stepsBetweenSelect_(stepsBetweenSelect),
72         stepsLeft_(0) {}
73
74   size_t operator()(size_t numActive) {
75     adjustPermSize(numActive);
76     if (stepsLeft_-- == 0) {
77       stepsLeft_ = stepsBetweenSelect_ - 1;
78       shufflePrefix();
79     }
80     return perm_[uniform_(std::min(numActive, subsetSize_))];
81   }
82
83  private:
84   std::function<int(int)> uniform_;
85   const size_t subsetSize_;
86   const int stepsBetweenSelect_;
87
88   int stepsLeft_;
89   // only the first subsetSize_ is properly randomized
90   std::vector<int> perm_;
91
92   void adjustPermSize(size_t numActive) {
93     if (perm_.size() > numActive) {
94       perm_.erase(std::remove_if(perm_.begin(), perm_.end(), [=](size_t x) {
95         return x >= numActive;
96       }), perm_.end());
97     } else {
98       while (perm_.size() < numActive) {
99         perm_.push_back(perm_.size());
100       }
101     }
102     assert(perm_.size() == numActive);
103   }
104
105   void shufflePrefix() {
106     for (size_t i = 0; i < std::min(perm_.size() - 1, subsetSize_); ++i) {
107       int j = uniform_(perm_.size() - i) + i;
108       std::swap(perm_[i], perm_[j]);
109     }
110   }
111 };
112
113 std::function<int(int)> DeterministicSchedule::uniformSubset(long seed,
114                                                              int n,
115                                                              int m) {
116   auto gen = std::make_shared<UniformSubset>(seed, n, m);
117   return [=](size_t numActive) { return (*gen)(numActive); };
118 }
119
120 void DeterministicSchedule::beforeSharedAccess() {
121   if (tls_sem) {
122     sem_wait(tls_sem);
123   }
124 }
125
126 void DeterministicSchedule::afterSharedAccess() {
127   auto sched = tls_sched;
128   if (!sched) {
129     return;
130   }
131
132   sem_post(sched->sems_[sched->scheduler_(sched->sems_.size())]);
133 }
134
135 int DeterministicSchedule::getRandNumber(int n) {
136   if (tls_sched) {
137     return tls_sched->scheduler_(n);
138   }
139   return std::rand() % n;
140 }
141
142 int DeterministicSchedule::getcpu(unsigned* cpu, unsigned* node, void* unused) {
143   if (!tls_threadId && tls_sched) {
144     beforeSharedAccess();
145     tls_threadId = tls_sched->nextThreadId_++;
146     afterSharedAccess();
147   }
148   if (cpu) {
149     *cpu = tls_threadId;
150   }
151   if (node) {
152     *node = tls_threadId;
153   }
154   return 0;
155 }
156
157 sem_t* DeterministicSchedule::beforeThreadCreate() {
158   sem_t* s = new sem_t;
159   sem_init(s, 0, 0);
160   beforeSharedAccess();
161   sems_.push_back(s);
162   afterSharedAccess();
163   return s;
164 }
165
166 void DeterministicSchedule::afterThreadCreate(sem_t* sem) {
167   assert(tls_sem == nullptr);
168   assert(tls_sched == nullptr);
169   tls_sem = sem;
170   tls_sched = this;
171   bool started = false;
172   while (!started) {
173     beforeSharedAccess();
174     if (active_.count(std::this_thread::get_id()) == 1) {
175       started = true;
176     }
177     afterSharedAccess();
178   }
179 }
180
181 void DeterministicSchedule::beforeThreadExit() {
182   assert(tls_sched == this);
183   beforeSharedAccess();
184   sems_.erase(std::find(sems_.begin(), sems_.end(), tls_sem));
185   active_.erase(std::this_thread::get_id());
186   if (sems_.size() > 0) {
187     FOLLY_TEST_DSCHED_VLOG("exiting");
188     afterSharedAccess();
189   }
190   sem_destroy(tls_sem);
191   delete tls_sem;
192   tls_sem = nullptr;
193   tls_sched = nullptr;
194 }
195
196 void DeterministicSchedule::join(std::thread& child) {
197   auto sched = tls_sched;
198   if (sched) {
199     bool done = false;
200     while (!done) {
201       beforeSharedAccess();
202       done = !sched->active_.count(child.get_id());
203       if (done) {
204         FOLLY_TEST_DSCHED_VLOG("joined " << std::hex << child.get_id());
205       }
206       afterSharedAccess();
207     }
208   }
209   child.join();
210 }
211
212 void DeterministicSchedule::post(sem_t* sem) {
213   beforeSharedAccess();
214   sem_post(sem);
215   FOLLY_TEST_DSCHED_VLOG("sem_post(" << sem << ")");
216   afterSharedAccess();
217 }
218
219 bool DeterministicSchedule::tryWait(sem_t* sem) {
220   beforeSharedAccess();
221   int rv = sem_trywait(sem);
222   int e = rv == 0 ? 0 : errno;
223   FOLLY_TEST_DSCHED_VLOG("sem_trywait(" << sem << ") = " << rv
224                                         << " errno=" << e);
225   afterSharedAccess();
226   if (rv == 0) {
227     return true;
228   } else {
229     assert(e == EAGAIN);
230     return false;
231   }
232 }
233
234 void DeterministicSchedule::wait(sem_t* sem) {
235   while (!tryWait(sem)) {
236     // we're not busy waiting because this is a deterministic schedule
237   }
238 }
239 }
240 }
241
242 namespace folly {
243 namespace detail {
244
245 using namespace test;
246 using namespace std::chrono;
247
248 template <>
249 FutexResult Futex<DeterministicAtomic>::futexWaitImpl(
250     uint32_t expected,
251     time_point<system_clock>* absSystemTimeout,
252     time_point<steady_clock>* absSteadyTimeout,
253     uint32_t waitMask) {
254   bool hasTimeout = absSystemTimeout != nullptr || absSteadyTimeout != nullptr;
255   bool awoken = false;
256   FutexResult result = FutexResult::AWOKEN;
257
258   DeterministicSchedule::beforeSharedAccess();
259   FOLLY_TEST_DSCHED_VLOG(this << ".futexWait(" << std::hex << expected
260                               << ", .., " << std::hex << waitMask
261                               << ") beginning..");
262   futexLock.lock();
263   if (data == expected) {
264     auto& queue = futexQueues[this];
265     queue.emplace_back(waitMask, &awoken);
266     auto ours = queue.end();
267     ours--;
268     while (!awoken) {
269       futexLock.unlock();
270       DeterministicSchedule::afterSharedAccess();
271       DeterministicSchedule::beforeSharedAccess();
272       futexLock.lock();
273
274       // Simulate spurious wake-ups, timeouts each time with
275       // a 10% probability if we haven't been woken up already
276       if (!awoken && hasTimeout &&
277           DeterministicSchedule::getRandNumber(100) < 10) {
278         assert(futexQueues.count(this) != 0 && &futexQueues[this] == &queue);
279         queue.erase(ours);
280         if (queue.empty()) {
281           futexQueues.erase(this);
282         }
283         // Simulate ETIMEDOUT 90% of the time and other failures
284         // remaining time
285         result = DeterministicSchedule::getRandNumber(100) >= 10
286                      ? FutexResult::TIMEDOUT
287                      : FutexResult::INTERRUPTED;
288         break;
289       }
290     }
291   } else {
292     result = FutexResult::VALUE_CHANGED;
293   }
294   futexLock.unlock();
295
296   char const* resultStr = "?";
297   switch (result) {
298   case FutexResult::AWOKEN:
299     resultStr = "AWOKEN";
300     break;
301   case FutexResult::TIMEDOUT:
302     resultStr = "TIMEDOUT";
303     break;
304   case FutexResult::INTERRUPTED:
305     resultStr = "INTERRUPTED";
306     break;
307   case FutexResult::VALUE_CHANGED:
308     resultStr = "VALUE_CHANGED";
309     break;
310   }
311   FOLLY_TEST_DSCHED_VLOG(this << ".futexWait(" << std::hex << expected
312                               << ", .., " << std::hex << waitMask << ") -> "
313                               << resultStr);
314   DeterministicSchedule::afterSharedAccess();
315   return result;
316 }
317
318 template <>
319 int Futex<DeterministicAtomic>::futexWake(int count, uint32_t wakeMask) {
320   int rv = 0;
321   DeterministicSchedule::beforeSharedAccess();
322   futexLock.lock();
323   if (futexQueues.count(this) > 0) {
324     auto& queue = futexQueues[this];
325     auto iter = queue.begin();
326     while (iter != queue.end() && rv < count) {
327       auto cur = iter++;
328       if ((cur->first & wakeMask) != 0) {
329         *(cur->second) = true;
330         rv++;
331         queue.erase(cur);
332       }
333     }
334     if (queue.empty()) {
335       futexQueues.erase(this);
336     }
337   }
338   futexLock.unlock();
339   FOLLY_TEST_DSCHED_VLOG(this << ".futexWake(" << count << ", " << std::hex
340                               << wakeMask << ") -> " << rv);
341   DeterministicSchedule::afterSharedAccess();
342   return rv;
343 }
344
345 template <>
346 CacheLocality const& CacheLocality::system<test::DeterministicAtomic>() {
347   static CacheLocality cache(CacheLocality::uniform(16));
348   return cache;
349 }
350
351 template <>
352 const AccessSpreader<test::DeterministicAtomic>
353     AccessSpreader<test::DeterministicAtomic>::stripeByCore(
354         CacheLocality::system<>().numCachesByLevel.front());
355
356 template <>
357 const AccessSpreader<test::DeterministicAtomic>
358     AccessSpreader<test::DeterministicAtomic>::stripeByChip(
359         CacheLocality::system<>().numCachesByLevel.back());
360
361 template <>
362 AccessSpreaderArray<test::DeterministicAtomic, 128>
363     AccessSpreaderArray<test::DeterministicAtomic, 128>::sharedInstance = {};
364
365 template <>
366 Getcpu::Func AccessSpreader<test::DeterministicAtomic>::pickGetcpuFunc(
367     size_t numStripes) {
368   return &DeterministicSchedule::getcpu;
369 }
370 }
371 }