Fix ThreadLocal races
[folly.git] / folly / synchronization / test / RcuTest.cpp
1 /*
2  * Copyright 2017-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 #include <folly/synchronization/Rcu.h>
17
18 #include <thread>
19 #include <vector>
20
21 #include <glog/logging.h>
22
23 #include <folly/Benchmark.h>
24 #include <folly/Random.h>
25 #include <folly/portability/GFlags.h>
26 #include <folly/portability/GTest.h>
27
28 using namespace folly;
29
30 DEFINE_int64(iters, 100000, "Number of iterations");
31 DEFINE_uint64(threads, 32, "Number of threads");
32
33 TEST(RcuTest, Basic) {
34   auto foo = new int(2);
35   rcu_retire(foo);
36 }
37
38 class des {
39   bool* d_;
40
41  public:
42   des(bool* d) : d_(d) {}
43   ~des() {
44     *d_ = true;
45   }
46 };
47
48 TEST(RcuTest, Guard) {
49   bool del = false;
50   auto foo = new des(&del);
51   { rcu_reader g; }
52   rcu_retire(foo);
53   synchronize_rcu();
54   EXPECT_TRUE(del);
55 }
56
57 TEST(RcuTest, Perf) {
58   long i = FLAGS_iters;
59   auto start = std::chrono::steady_clock::now();
60   while (i-- > 0) {
61     rcu_reader g;
62   }
63   auto diff = std::chrono::steady_clock::now() - start;
64   printf(
65       "Total time %li ns \n",
66       std::chrono::duration_cast<std::chrono::nanoseconds>(diff).count() /
67           FLAGS_iters);
68 }
69
70 TEST(RcuTest, ResetPerf) {
71   long i = FLAGS_iters;
72   auto start = std::chrono::steady_clock::now();
73   while (i-- > 0) {
74     rcu_retire<int>(nullptr, [](int*) {});
75   }
76   auto diff = std::chrono::steady_clock::now() - start;
77   printf(
78       "Total time %li ns \n",
79       std::chrono::duration_cast<std::chrono::nanoseconds>(diff).count() /
80           FLAGS_iters);
81 }
82
83 TEST(RcuTest, SlowReader) {
84   std::thread t;
85   {
86     rcu_reader g;
87
88     t = std::thread([&]() { synchronize_rcu(); });
89     usleep(100); // Wait for synchronize to start
90   }
91   t.join();
92 }
93
94 rcu_reader tryretire(des* obj) {
95   rcu_reader g;
96   rcu_retire(obj);
97   return g;
98 }
99
100 TEST(RcuTest, CopyGuard) {
101   bool del = false;
102   auto foo = new des(&del);
103   {
104     auto res = tryretire(foo);
105     EXPECT_FALSE(del);
106   }
107   rcu_barrier();
108   EXPECT_TRUE(del);
109 }
110
111 TEST(RcuTest, Stress) {
112   std::vector<std::thread> threads;
113   constexpr uint32_t sz = 1000;
114   std::atomic<int*> ints[sz];
115   for (uint i = 0; i < sz; i++) {
116     ints[i].store(new int(0));
117   }
118   for (unsigned th = 0; th < FLAGS_threads; th++) {
119     threads.push_back(std::thread([&]() {
120       for (int i = 0; i < FLAGS_iters / 100; i++) {
121         rcu_reader g;
122         int sum = 0;
123         int* ptrs[sz];
124         for (uint j = 0; j < sz; j++) {
125           ptrs[j] = ints[j].load(std::memory_order_acquire);
126         }
127         for (uint j = 0; j < sz; j++) {
128           sum += *ptrs[j];
129         }
130         EXPECT_EQ(sum, 0);
131       }
132     }));
133   }
134   std::atomic<bool> done{false};
135   std::thread updater([&]() {
136     while (!done.load()) {
137       auto newint = new int(0);
138       auto oldint = ints[folly::Random::rand32() % sz].exchange(newint);
139       rcu_retire<int>(oldint, [](int* obj) {
140         *obj = folly::Random::rand32();
141         delete obj;
142       });
143     }
144   });
145   for (auto& t : threads) {
146     t.join();
147   }
148   done = true;
149   updater.join();
150   // Cleanup for asan
151   synchronize_rcu();
152   for (uint i = 0; i < sz; i++) {
153     delete ints[i].exchange(nullptr);
154   }
155 }
156
157 TEST(RcuTest, Synchronize) {
158   std::vector<std::thread> threads;
159   for (unsigned th = 0; th < FLAGS_threads; th++) {
160     threads.push_back(std::thread([&]() {
161       for (int i = 0; i < 10; i++) {
162         synchronize_rcu();
163       }
164     }));
165   }
166   for (auto& t : threads) {
167     t.join();
168   }
169 }
170
171 TEST(RcuTest, NewDomainTest) {
172   struct UniqueTag;
173   rcu_domain<UniqueTag> newdomain(nullptr);
174   synchronize_rcu();
175 }
176
177 TEST(RcuTest, MovableReader) {
178   {
179     rcu_reader g;
180     rcu_reader f(std::move(g));
181   }
182   synchronize_rcu();
183   {
184     rcu_reader g(std::defer_lock);
185     rcu_reader f;
186     g = std::move(f);
187   }
188   synchronize_rcu();
189 }
190
191 TEST(RcuTest, SynchronizeInCall) {
192   rcu_default_domain()->call([]() { synchronize_rcu(); });
193   synchronize_rcu();
194 }
195
196 TEST(RcuTest, MoveReaderBetweenThreads) {
197   rcu_reader g;
198   std::thread t([f = std::move(g)] {});
199   t.join();
200   synchronize_rcu();
201 }
202
203 TEST(RcuTest, ForkTest) {
204   rcu_token epoch;
205   std::thread t([&]() {
206     epoch = rcu_default_domain()->lock_shared();
207   });
208   t.join();
209   auto pid = fork();
210   if (pid) {
211     // parent
212     rcu_default_domain()->unlock_shared(std::move(epoch));
213     synchronize_rcu();
214     int status;
215     auto pid2 = wait(&status);
216     EXPECT_EQ(status, 0);
217     EXPECT_EQ(pid, pid2);
218   } else {
219     // child
220     synchronize_rcu();
221     exit(0); // Do not print gtest results
222   }
223 }
224
225 TEST(RcuTest, ThreadLocalList) {
226   struct TTag;
227   folly::detail::ThreadCachedLists<TTag> lists;
228   std::vector<std::thread> threads{FLAGS_threads};
229   std::atomic<unsigned long> done{FLAGS_threads};
230   for (auto& tr : threads) {
231     tr = std::thread([&]() {
232       for (int i = 0; i < FLAGS_iters; i++) {
233         auto node = new folly::detail::ThreadCachedListsBase::Node;
234         lists.push(node);
235       }
236       --done;
237     });
238   }
239   while (done.load() > 0) {
240     folly::detail::ThreadCachedLists<TTag>::ListHead list{};
241     lists.collect(list);
242     list.forEach([](folly::detail::ThreadCachedLists<TTag>::Node* node) {
243       delete node;
244     });
245   }
246   for (auto& thread : threads) {
247     thread.join();
248   }
249   // Run cleanup pass one more time to make ASAN happy
250   folly::detail::ThreadCachedLists<TTag>::ListHead list{};
251   lists.collect(list);
252   list.forEach(
253       [](folly::detail::ThreadCachedLists<TTag>::Node* node) { delete node; });
254 }
255
256 TEST(RcuTest, ThreadDeath) {
257   bool del = false;
258   std::thread t([&] {
259     auto foo = new des(&del);
260     rcu_retire(foo);
261   });
262   t.join();
263   synchronize_rcu();
264   EXPECT_TRUE(del);
265 }
266
267 TEST(RcuTest, RcuObjBase) {
268   bool retired = false;
269   struct base_test : rcu_obj_base<base_test> {
270     bool* ret_;
271     base_test(bool* ret) : ret_(ret) {}
272     ~base_test() {
273       (*ret_) = true;
274     }
275   };
276
277   auto foo = new base_test(&retired);
278   foo->retire();
279   synchronize_rcu();
280   EXPECT_TRUE(retired);
281 }