1 /*
2 * Copyright (c) Facebook, Inc. and its affiliates.
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/ThreadLocal.h>
18
19 #ifndef _WIN32
20 #include <dlfcn.h>
21 #include <sys/wait.h>
22 #endif
23
24 #include <sys/types.h>
25
26 #include <array>
27 #include <atomic>
28 #include <chrono>
29 #include <climits>
30 #include <condition_variable>
31 #include <map>
32 #include <memory>
33 #include <mutex>
34 #include <set>
35 #include <thread>
36 #include <unordered_map>
37
38 #include <boost/thread/barrier.hpp>
39 #include <glog/logging.h>
40
41 #include <folly/Memory.h>
42 #include <folly/experimental/io/FsUtil.h>
43 #include <folly/portability/GTest.h>
44 #include <folly/portability/Unistd.h>
45 #include <folly/synchronization/Baton.h>
46 #include <folly/synchronization/detail/ThreadCachedInts.h>
47 #include <folly/system/ThreadId.h>
48
49 using namespace folly;
50
51 struct Widget {
52 static int totalVal_;
53 static int totalMade_;
54 int val_;
WidgetWidget55 Widget() : val_(0) { totalMade_++; }
~WidgetWidget56 ~Widget() { totalVal_ += val_; }
57
customDeleterWidget58 static void customDeleter(Widget* w, TLPDestructionMode mode) {
59 totalVal_ += (mode == TLPDestructionMode::ALL_THREADS) ? 1000 : 1;
60 delete w;
61 }
62 };
63 int Widget::totalVal_ = 0;
64 int Widget::totalMade_ = 0;
65
66 struct MultiWidget {
67 int val_{0};
68 MultiWidget() = default;
~MultiWidgetMultiWidget69 ~MultiWidget() {
70 // force a reallocation in the destructor by
71 // allocating more than elementsCapacity
72
73 using TL = ThreadLocal<size_t>;
74 using TLMeta = threadlocal_detail::static_meta_of<TL>::type;
75 auto const numElements = TLMeta::instance().elementsCapacity() + 1;
76 std::vector<ThreadLocal<size_t>> elems(numElements);
77 for (auto& t : elems) {
78 *t += 1;
79 }
80 }
81 };
82
TEST(ThreadLocalPtr,BasicDestructor)83 TEST(ThreadLocalPtr, BasicDestructor) {
84 Widget::totalVal_ = 0;
85 ThreadLocalPtr<Widget> w;
86 std::thread([&w]() {
87 w.reset(new Widget());
88 w.get()->val_ += 10;
89 }).join();
90 EXPECT_EQ(10, Widget::totalVal_);
91 }
92
TEST(ThreadLocalPtr,CustomDeleter1)93 TEST(ThreadLocalPtr, CustomDeleter1) {
94 Widget::totalVal_ = 0;
95 {
96 ThreadLocalPtr<Widget> w;
97 std::thread([&w]() {
98 w.reset(new Widget(), Widget::customDeleter);
99 w.get()->val_ += 10;
100 }).join();
101 EXPECT_EQ(11, Widget::totalVal_);
102 }
103 EXPECT_EQ(11, Widget::totalVal_);
104 }
105
TEST(ThreadLocalPtr,CustomDeleterOwnershipTransfer)106 TEST(ThreadLocalPtr, CustomDeleterOwnershipTransfer) {
107 Widget::totalVal_ = 0;
108 {
109 ThreadLocalPtr<Widget> w;
110 auto deleter = [](Widget* ptr) {
111 Widget::customDeleter(ptr, TLPDestructionMode::THIS_THREAD);
112 };
113 std::unique_ptr<Widget, decltype(deleter)> source(new Widget(), deleter);
114 std::thread([&w, &source]() {
115 w.reset(std::move(source));
116 w.get()->val_ += 10;
117 }).join();
118 EXPECT_EQ(11, Widget::totalVal_);
119 }
120 EXPECT_EQ(11, Widget::totalVal_);
121 }
122
TEST(ThreadLocalPtr,DefaultDeleterOwnershipTransfer)123 TEST(ThreadLocalPtr, DefaultDeleterOwnershipTransfer) {
124 Widget::totalVal_ = 0;
125 {
126 ThreadLocalPtr<Widget> w;
127 auto source = std::make_unique<Widget>();
128 std::thread([&w, &source]() {
129 w.reset(std::move(source));
130 w.get()->val_ += 10;
131 }).join();
132 EXPECT_EQ(10, Widget::totalVal_);
133 }
134 EXPECT_EQ(10, Widget::totalVal_);
135 }
136
TEST(ThreadLocalPtr,resetNull)137 TEST(ThreadLocalPtr, resetNull) {
138 ThreadLocalPtr<int> tl;
139 EXPECT_FALSE(tl);
140 tl.reset(new int(4));
141 EXPECT_TRUE(static_cast<bool>(tl));
142 EXPECT_EQ(*tl.get(), 4);
143 tl.reset();
144 EXPECT_FALSE(tl);
145 }
146
TEST(ThreadLocalPtr,TestRelease)147 TEST(ThreadLocalPtr, TestRelease) {
148 Widget::totalVal_ = 0;
149 ThreadLocalPtr<Widget> w;
150 std::unique_ptr<Widget> wPtr;
151 std::thread([&w, &wPtr]() {
152 w.reset(new Widget());
153 w.get()->val_ += 10;
154
155 wPtr.reset(w.release());
156 }).join();
157 EXPECT_EQ(0, Widget::totalVal_);
158 wPtr.reset();
159 EXPECT_EQ(10, Widget::totalVal_);
160 }
161
TEST(ThreadLocalPtr,CreateOnThreadExit)162 TEST(ThreadLocalPtr, CreateOnThreadExit) {
163 Widget::totalVal_ = 0;
164 ThreadLocal<Widget> w;
165 ThreadLocalPtr<int> tl;
166
167 std::thread([&] {
168 tl.reset(new int(1), [&](int* ptr, TLPDestructionMode /* mode */) {
169 delete ptr;
170 // This test ensures Widgets allocated here are not leaked.
171 ++w.get()->val_;
172 ThreadLocal<Widget> wl;
173 ++wl.get()->val_;
174 });
175 }).join();
176 EXPECT_EQ(2, Widget::totalVal_);
177 }
178
179 // Test deleting the ThreadLocalPtr object
TEST(ThreadLocalPtr,CustomDeleter2)180 TEST(ThreadLocalPtr, CustomDeleter2) {
181 Widget::totalVal_ = 0;
182 std::thread t;
183 std::mutex mutex;
184 std::condition_variable cv;
185 enum class State {
186 START,
187 DONE,
188 EXIT,
189 };
190 State state = State::START;
191 {
192 ThreadLocalPtr<Widget> w;
193 t = std::thread([&]() {
194 w.reset(new Widget(), Widget::customDeleter);
195 w.get()->val_ += 10;
196
197 // Notify main thread that we're done
198 {
199 std::unique_lock<std::mutex> lock(mutex);
200 state = State::DONE;
201 cv.notify_all();
202 }
203
204 // Wait for main thread to allow us to exit
205 {
206 std::unique_lock<std::mutex> lock(mutex);
207 while (state != State::EXIT) {
208 cv.wait(lock);
209 }
210 }
211 });
212
213 // Wait for main thread to start (and set w.get()->val_)
214 {
215 std::unique_lock<std::mutex> lock(mutex);
216 while (state != State::DONE) {
217 cv.wait(lock);
218 }
219 }
220
221 // Thread started but hasn't exited yet
222 EXPECT_EQ(0, Widget::totalVal_);
223
224 // Destroy ThreadLocalPtr<Widget> (by letting it go out of scope)
225 }
226
227 EXPECT_EQ(1010, Widget::totalVal_);
228
229 // Allow thread to exit
230 {
231 std::unique_lock<std::mutex> lock(mutex);
232 state = State::EXIT;
233 cv.notify_all();
234 }
235 t.join();
236
237 EXPECT_EQ(1010, Widget::totalVal_);
238 }
239
TEST(ThreadLocal,GetWithoutCreateUncreated)240 TEST(ThreadLocal, GetWithoutCreateUncreated) {
241 Widget::totalVal_ = 0;
242 Widget::totalMade_ = 0;
243 ThreadLocal<Widget> w;
244 std::thread([&w]() {
245 auto ptr = w.getIfExist();
246 if (ptr) {
247 ptr->val_++;
248 }
249 }).join();
250 EXPECT_EQ(0, Widget::totalMade_);
251 }
252
TEST(ThreadLocal,GetWithoutCreateGets)253 TEST(ThreadLocal, GetWithoutCreateGets) {
254 Widget::totalVal_ = 0;
255 Widget::totalMade_ = 0;
256 ThreadLocal<Widget> w;
257 std::thread([&w]() {
258 w->val_++;
259 auto ptr = w.getIfExist();
260 if (ptr) {
261 ptr->val_++;
262 }
263 }).join();
264 EXPECT_EQ(1, Widget::totalMade_);
265 EXPECT_EQ(2, Widget::totalVal_);
266 }
267
TEST(ThreadLocal,BasicDestructor)268 TEST(ThreadLocal, BasicDestructor) {
269 Widget::totalVal_ = 0;
270 ThreadLocal<Widget> w;
271 std::thread([&w]() { w->val_ += 10; }).join();
272 EXPECT_EQ(10, Widget::totalVal_);
273 }
274
275 // this should force a realloc of the ElementWrapper array
TEST(ThreadLocal,ReallocDestructor)276 TEST(ThreadLocal, ReallocDestructor) {
277 ThreadLocal<MultiWidget> w;
278 std::thread([&w]() { w->val_ += 10; }).join();
279 }
280
TEST(ThreadLocal,SimpleRepeatDestructor)281 TEST(ThreadLocal, SimpleRepeatDestructor) {
282 Widget::totalVal_ = 0;
283 {
284 ThreadLocal<Widget> w;
285 w->val_ += 10;
286 }
287 {
288 ThreadLocal<Widget> w;
289 w->val_ += 10;
290 }
291 EXPECT_EQ(20, Widget::totalVal_);
292 }
293
TEST(ThreadLocal,InterleavedDestructors)294 TEST(ThreadLocal, InterleavedDestructors) {
295 Widget::totalVal_ = 0;
296 std::unique_ptr<ThreadLocal<Widget>> w;
297 int wVersion = 0;
298 const int wVersionMax = 2;
299 int thIter = 0;
300 std::mutex lock;
301 auto th = std::thread([&]() {
302 int wVersionPrev = 0;
303 while (true) {
304 while (true) {
305 std::lock_guard<std::mutex> g(lock);
306 if (wVersion > wVersionMax) {
307 return;
308 }
309 if (wVersion > wVersionPrev) {
310 // We have a new version of w, so it should be initialized to zero
311 EXPECT_EQ((*w)->val_, 0);
312 break;
313 }
314 }
315 std::lock_guard<std::mutex> g(lock);
316 wVersionPrev = wVersion;
317 (*w)->val_ += 10;
318 ++thIter;
319 }
320 });
321 FOR_EACH_RANGE (i, 0, wVersionMax) {
322 int thIterPrev = 0;
323 {
324 std::lock_guard<std::mutex> g(lock);
325 thIterPrev = thIter;
326 w = std::make_unique<ThreadLocal<Widget>>();
327 ++wVersion;
328 }
329 while (true) {
330 std::lock_guard<std::mutex> g(lock);
331 if (thIter > thIterPrev) {
332 break;
333 }
334 }
335 }
336 {
337 std::lock_guard<std::mutex> g(lock);
338 wVersion = wVersionMax + 1;
339 }
340 th.join();
341 EXPECT_EQ(wVersionMax * 10, Widget::totalVal_);
342 }
343
344 class SimpleThreadCachedInt {
345 class NewTag;
346 ThreadLocal<int, NewTag> val_;
347
348 public:
add(int val)349 void add(int val) { *val_ += val; }
350
read()351 int read() {
352 int ret = 0;
353 for (const auto& i : val_.accessAllThreads()) {
354 ret += i;
355 }
356 return ret;
357 }
358 };
359
TEST(ThreadLocalPtr,AccessAllThreadsCounter)360 TEST(ThreadLocalPtr, AccessAllThreadsCounter) {
361 const int kNumThreads = 256;
362 SimpleThreadCachedInt stci[kNumThreads + 1];
363 std::atomic<bool> run(true);
364 std::atomic<int> totalAtomic{0};
365 std::vector<std::thread> threads;
366 // thread i will increment all the thread locals
367 // in the range 0..i
368 for (int i = 0; i < kNumThreads; ++i) {
369 threads.push_back(std::thread([i, // i needs to be captured by value
370 &stci,
371 &run,
372 &totalAtomic]() {
373 for (int j = 0; j <= i; j++) {
374 stci[j].add(1);
375 }
376
377 totalAtomic.fetch_add(1);
378 while (run.load()) {
379 usleep(100);
380 }
381 }));
382 }
383 while (totalAtomic.load() != kNumThreads) {
384 usleep(100);
385 }
386 for (int i = 0; i <= kNumThreads; i++) {
387 EXPECT_EQ(kNumThreads - i, stci[i].read());
388 }
389 run.store(false);
390 for (auto& t : threads) {
391 t.join();
392 }
393 }
394
TEST(ThreadLocal,resetNull)395 TEST(ThreadLocal, resetNull) {
396 ThreadLocal<int> tl;
397 tl.reset(new int(4));
398 EXPECT_EQ(*tl.get(), 4);
399 tl.reset();
400 EXPECT_EQ(*tl.get(), 0);
401 tl.reset(new int(5));
402 EXPECT_EQ(*tl.get(), 5);
403 }
404
405 namespace {
406 struct Tag {};
407
408 struct Foo {
409 folly::ThreadLocal<int, Tag> tl;
410 };
411 } // namespace
412
TEST(ThreadLocal,Movable1)413 TEST(ThreadLocal, Movable1) {
414 Foo a;
415 Foo b;
416 EXPECT_TRUE(a.tl.get() != b.tl.get());
417
418 a = Foo();
419 b = Foo();
420 EXPECT_TRUE(a.tl.get() != b.tl.get());
421 }
422
TEST(ThreadLocal,Movable2)423 TEST(ThreadLocal, Movable2) {
424 std::map<int, Foo> map;
425
426 map[42];
427 map[10];
428 map[23];
429 map[100];
430
431 std::set<void*> tls;
432 for (auto& m : map) {
433 tls.insert(m.second.tl.get());
434 }
435
436 // Make sure that we have 4 different instances of *tl
437 EXPECT_EQ(4, tls.size());
438 }
439
440 namespace {
441 class ThreadCachedIntWidget {
442 public:
ThreadCachedIntWidget()443 ThreadCachedIntWidget() {}
444
~ThreadCachedIntWidget()445 ~ThreadCachedIntWidget() {
446 if (ints_) {
447 ints_->increment(0);
448 }
449 }
450
set(detail::ThreadCachedInts<void> * ints)451 void set(detail::ThreadCachedInts<void>* ints) { ints_ = ints; }
452
453 private:
454 detail::ThreadCachedInts<void>* ints_{nullptr};
455 };
456 } // namespace
457
TEST(ThreadLocal,TCICreateOnThreadExit)458 TEST(ThreadLocal, TCICreateOnThreadExit) {
459 detail::ThreadCachedInts<void> ints;
460 ThreadLocal<ThreadCachedIntWidget> w;
461
462 std::thread([&] {
463 // make sure the ints object is created
464 ints.increment(1);
465 // now the widget
466 w->set(&ints);
467 }).join();
468 }
469
470 namespace {
471
472 constexpr size_t kFillObjectSize = 300;
473
474 std::atomic<uint64_t> gDestroyed;
475
476 /**
477 * Fill a chunk of memory with a unique-ish pattern that includes the thread id
478 * (so deleting one of these from another thread would cause a failure)
479 *
480 * Verify it explicitly and on destruction.
481 */
482 class FillObject {
483 public:
FillObject(uint64_t idx)484 explicit FillObject(uint64_t idx) : idx_(idx) {
485 uint64_t v = val();
486 for (size_t i = 0; i < kFillObjectSize; ++i) {
487 data_[i] = v;
488 }
489 }
490
check()491 void check() {
492 uint64_t v = val();
493 for (size_t i = 0; i < kFillObjectSize; ++i) {
494 CHECK_EQ(v, data_[i]);
495 }
496 }
497
~FillObject()498 ~FillObject() { ++gDestroyed; }
499
500 private:
val() const501 uint64_t val() const { return (idx_ << 40) | folly::getCurrentThreadID(); }
502
503 uint64_t idx_;
504 uint64_t data_[kFillObjectSize];
505 };
506
507 } // namespace
508
TEST(ThreadLocal,Stress)509 TEST(ThreadLocal, Stress) {
510 static constexpr size_t numFillObjects = 250;
511 std::array<ThreadLocalPtr<FillObject>, numFillObjects> objects;
512
513 static constexpr size_t numThreads = 32;
514 static constexpr size_t numReps = 20;
515
516 std::vector<std::thread> threads;
517 threads.reserve(numThreads);
518
519 for (size_t k = 0; k < numThreads; ++k) {
520 threads.emplace_back([&objects] {
521 for (size_t rep = 0; rep < numReps; ++rep) {
522 for (size_t i = 0; i < objects.size(); ++i) {
523 objects[i].reset(new FillObject(rep * objects.size() + i));
524 std::this_thread::sleep_for(std::chrono::microseconds(100));
525 }
526 for (size_t i = 0; i < objects.size(); ++i) {
527 objects[i]->check();
528 }
529 }
530 });
531 }
532
533 for (auto& t : threads) {
534 t.join();
535 }
536
537 EXPECT_EQ(numFillObjects * numThreads * numReps, gDestroyed);
538 }
539
540 struct StressAccessTag {};
541 using TLPInt = ThreadLocalPtr<int, Tag>;
542
tlpIntCustomDeleter(int * p,TLPDestructionMode)543 static void tlpIntCustomDeleter(int* p, TLPDestructionMode /*unused*/) {
544 delete p;
545 }
546
547 template <typename Op, typename Check>
StresAccessTest(Op op,Check check)548 void StresAccessTest(Op op, Check check) {
549 static constexpr size_t kNumThreads = 16;
550 static constexpr size_t kNumLoops = 10000;
551
552 TLPInt ptr;
553 ptr.reset(new int(0));
554 std::atomic<bool> running{true};
555
556 boost::barrier barrier(kNumThreads + 1);
557
558 std::vector<std::thread> threads;
559
560 for (size_t k = 0; k < kNumThreads; ++k) {
561 threads.emplace_back([&] {
562 ptr.reset(new int(1));
563
564 barrier.wait();
565
566 while (running.load()) {
567 op(ptr);
568 }
569 });
570 }
571
572 // wait for the threads to be up and running
573 barrier.wait();
574
575 for (size_t n = 0; n < kNumLoops; n++) {
576 int sum = 0;
577 auto accessor = ptr.accessAllThreads();
578 for (auto& i : accessor) {
579 sum += i;
580 }
581
582 check(sum, kNumThreads);
583 }
584
585 running.store(false);
586 for (auto& t : threads) {
587 t.join();
588 }
589 }
590
TEST(ThreadLocal,StressAccessReset)591 TEST(ThreadLocal, StressAccessReset) {
592 StresAccessTest(
593 [](TLPInt& ptr) { ptr.reset(new int(1)); },
594 [](size_t sum, size_t numThreads) { EXPECT_EQ(sum, numThreads); });
595 }
596
TEST(ThreadLocal,StressAccessResetDeleter)597 TEST(ThreadLocal, StressAccessResetDeleter) {
598 StresAccessTest(
599 [](TLPInt& ptr) { ptr.reset(new int(1), tlpIntCustomDeleter); },
600 [](size_t sum, size_t numThreads) { EXPECT_EQ(sum, numThreads); });
601 }
602
TEST(ThreadLocal,StressAccessRelease)603 TEST(ThreadLocal, StressAccessRelease) {
604 StresAccessTest(
605 [](TLPInt& ptr) {
606 auto* p = ptr.release();
607 delete p;
608 ptr.reset(new int(1));
609 },
610 [](size_t sum, size_t numThreads) { EXPECT_LE(sum, numThreads); });
611 }
612
613 // Yes, threads and fork don't mix
614 // (http://cppwisdom.quora.com/Why-threads-and-fork-dont-mix) but if you're
615 // stupid or desperate enough to try, we shouldn't stand in your way.
616 namespace {
617 class HoldsOne {
618 public:
HoldsOne()619 HoldsOne() : value_(1) {}
620 // Do an actual access to catch the buggy case where this == nullptr
value() const621 int value() const { return value_; }
622
623 private:
624 int value_;
625 };
626
627 struct HoldsOneTag {};
628
629 ThreadLocal<HoldsOne, HoldsOneTag> ptr;
630
totalValue()631 int totalValue() {
632 int value = 0;
633 for (auto& p : ptr.accessAllThreads()) {
634 value += p.value();
635 }
636 return value;
637 }
638
639 } // namespace
640
641 #ifdef FOLLY_HAVE_PTHREAD_ATFORK
TEST(ThreadLocal,Fork)642 TEST(ThreadLocal, Fork) {
643 EXPECT_EQ(1, ptr->value()); // ensure created
644 EXPECT_EQ(1, totalValue());
645 // Spawn a new thread
646
647 std::mutex mutex;
648 bool started = false;
649 std::condition_variable startedCond;
650 bool stopped = false;
651 std::condition_variable stoppedCond;
652
653 std::thread t([&]() {
654 EXPECT_EQ(1, ptr->value()); // ensure created
655 {
656 std::unique_lock<std::mutex> lock(mutex);
657 started = true;
658 startedCond.notify_all();
659 }
660 {
661 std::unique_lock<std::mutex> lock(mutex);
662 while (!stopped) {
663 stoppedCond.wait(lock);
664 }
665 }
666 });
667
668 {
669 std::unique_lock<std::mutex> lock(mutex);
670 while (!started) {
671 startedCond.wait(lock);
672 }
673 }
674
675 EXPECT_EQ(2, totalValue());
676
677 pid_t pid = fork();
678 if (pid == 0) {
679 // in child
680 int v = totalValue();
681
682 // exit successfully if v == 1 (one thread)
683 // diagnostic error code otherwise :)
684 switch (v) {
685 case 1:
686 _exit(0);
687 case 0:
688 _exit(1);
689 }
690 _exit(2);
691 } else if (pid > 0) {
692 // in parent
693 int status;
694 EXPECT_EQ(pid, waitpid(pid, &status, 0));
695 EXPECT_TRUE(WIFEXITED(status));
696 EXPECT_EQ(0, WEXITSTATUS(status));
697 } else {
698 ADD_FAILURE() << "fork failed";
699 }
700
701 EXPECT_EQ(2, totalValue());
702
703 {
704 std::unique_lock<std::mutex> lock(mutex);
705 stopped = true;
706 stoppedCond.notify_all();
707 }
708
709 t.join();
710
711 EXPECT_EQ(1, totalValue());
712 }
713 #endif
714
715 #ifndef _WIN32
716 struct HoldsOneTag2 {};
717
TEST(ThreadLocal,Fork2)718 TEST(ThreadLocal, Fork2) {
719 // A thread-local tag that was used in the parent from a *different* thread
720 // (but not the forking thread) would cause the child to hang in a
721 // ThreadLocalPtr's object destructor. Yeah.
722 ThreadLocal<HoldsOne, HoldsOneTag2> p;
723 {
724 // use tag in different thread
725 std::thread t([&p] { p.get(); });
726 t.join();
727 }
728 pid_t pid = fork();
729 if (pid == 0) {
730 {
731 ThreadLocal<HoldsOne, HoldsOneTag2> q;
732 q.get();
733 }
734 _exit(0);
735 } else if (pid > 0) {
736 int status;
737 EXPECT_EQ(pid, waitpid(pid, &status, 0));
738 EXPECT_TRUE(WIFEXITED(status));
739 EXPECT_EQ(0, WEXITSTATUS(status));
740 } else {
741 ADD_FAILURE() << "fork failed";
742 }
743 }
744
745 // Disable the SharedLibrary test when using any sanitizer. Otherwise, the
746 // dlopen'ed code would end up running without e.g., ASAN-initialized data
747 // structures and failing right away.
748 //
749 // We also cannot run this test unless folly was compiled with PIC support,
750 // since we cannot build thread_local_test_lib.so without PIC.
751 #if defined FOLLY_SANITIZE_ADDRESS || defined FOLLY_SANITIZE_THREAD || \
752 !defined FOLLY_SUPPORT_SHARED_LIBRARY
753 #define SHARED_LIBRARY_TEST_NAME DISABLED_SharedLibrary
754 #else
755 #define SHARED_LIBRARY_TEST_NAME SharedLibrary
756 #endif
757
TEST(ThreadLocal,SHARED_LIBRARY_TEST_NAME)758 TEST(ThreadLocal, SHARED_LIBRARY_TEST_NAME) {
759 auto exe = fs::executable_path();
760 auto lib = exe.parent_path() / "thread_local_test_lib.so";
761 auto handle = dlopen(lib.string().c_str(), RTLD_LAZY);
762 ASSERT_NE(nullptr, handle)
763 << "unable to load " << lib.string() << ": " << dlerror();
764
765 typedef void (*useA_t)();
766 dlerror();
767 useA_t useA = (useA_t)dlsym(handle, "useA");
768
769 const char* dlsym_error = dlerror();
770 EXPECT_EQ(nullptr, dlsym_error);
771 ASSERT_NE(nullptr, useA);
772
773 useA();
774
775 folly::Baton<> b11, b12, b21, b22;
776
777 std::thread t1([&]() {
778 useA();
779 b11.post();
780 b12.wait();
781 });
782
783 std::thread t2([&]() {
784 useA();
785 b21.post();
786 b22.wait();
787 });
788
789 b11.wait();
790 b21.wait();
791
792 dlclose(handle);
793
794 b12.post();
795 b22.post();
796
797 t1.join();
798 t2.join();
799 }
800
801 #endif
802
803 namespace folly {
804 namespace threadlocal_detail {
805 struct PthreadKeyUnregisterTester {
806 PthreadKeyUnregister p;
807 constexpr PthreadKeyUnregisterTester() = default;
808 };
809 } // namespace threadlocal_detail
810 } // namespace folly
811
TEST(ThreadLocal,UnregisterClassHasConstExprCtor)812 TEST(ThreadLocal, UnregisterClassHasConstExprCtor) {
813 folly::threadlocal_detail::PthreadKeyUnregisterTester x;
814 // yep!
815 SUCCEED();
816 }
817