clang-tools  11.0.0
ThreadingTests.cpp
Go to the documentation of this file.
1 //===-- ThreadingTests.cpp --------------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "support/Threading.h"
10 #include "llvm/ADT/DenseMap.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
13 #include <mutex>
14 
15 namespace clang {
16 namespace clangd {
17 class ThreadingTest : public ::testing::Test {};
18 
19 TEST_F(ThreadingTest, TaskRunner) {
20  const int TasksCnt = 100;
21  // This should be const, but MSVC does not allow to use const vars in lambdas
22  // without capture. On the other hand, clang gives a warning that capture of
23  // const var is not required.
24  // Making it non-const makes both compilers happy.
25  int IncrementsPerTask = 1000;
26 
27  std::mutex Mutex;
28  int Counter(0); /* GUARDED_BY(Mutex) */
29  {
30  AsyncTaskRunner Tasks;
31  auto scheduleIncrements = [&]() {
32  for (int TaskI = 0; TaskI < TasksCnt; ++TaskI) {
33  Tasks.runAsync("task", [&Counter, &Mutex, IncrementsPerTask]() {
34  for (int Increment = 0; Increment < IncrementsPerTask; ++Increment) {
35  std::lock_guard<std::mutex> Lock(Mutex);
36  ++Counter;
37  }
38  });
39  }
40  };
41 
42  {
43  // Make sure runAsync is not running tasks synchronously on the same
44  // thread by locking the Mutex used for increments.
45  std::lock_guard<std::mutex> Lock(Mutex);
46  scheduleIncrements();
47  }
48 
49  Tasks.wait();
50  {
51  std::lock_guard<std::mutex> Lock(Mutex);
52  ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask);
53  }
54 
55  {
56  std::lock_guard<std::mutex> Lock(Mutex);
57  Counter = 0;
58  scheduleIncrements();
59  }
60  }
61  // Check that destructor has waited for tasks to finish.
62  std::lock_guard<std::mutex> Lock(Mutex);
63  ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask);
64 }
65 
67  const unsigned NumThreads = 5;
68  const unsigned NumKeys = 100;
69  const unsigned NumIterations = 100;
70 
72  std::atomic<unsigned> ComputeCount(0);
73  std::atomic<int> ComputeResult[NumKeys];
74  std::fill(std::begin(ComputeResult), std::end(ComputeResult), -1);
75 
76  AsyncTaskRunner Tasks;
77  for (unsigned I = 0; I < NumThreads; ++I)
78  Tasks.runAsync("worker" + std::to_string(I), [&] {
79  for (unsigned J = 0; J < NumIterations; J++)
80  for (unsigned K = 0; K < NumKeys; K++) {
81  int Result = Cache.get(K, [&] { return ++ComputeCount; });
82  EXPECT_THAT(ComputeResult[K].exchange(Result),
83  testing::AnyOf(-1, Result))
84  << "Got inconsistent results from memoize";
85  }
86  });
87  Tasks.wait();
88  EXPECT_GE(ComputeCount, NumKeys) << "Computed each key once";
89  EXPECT_LE(ComputeCount, NumThreads * NumKeys)
90  << "Worst case, computed each key in every thread";
91  for (int Result : ComputeResult)
92  EXPECT_GT(Result, 0) << "All results in expected domain";
93 }
94 
95 TEST_F(ThreadingTest, MemoizeDeterministic) {
97 
98  // Spawn two parallel computations, A and B.
99  // Force concurrency: neither can finish until both have started.
100  // Verify that cache returns consistent results.
101  AsyncTaskRunner Tasks;
102  std::atomic<char> ValueA(0), ValueB(0);
103  Notification ReleaseA, ReleaseB;
104  Tasks.runAsync("A", [&] {
105  ValueA = Cache.get(0, [&] {
106  ReleaseB.notify();
107  ReleaseA.wait();
108  return 'A';
109  });
110  });
111  Tasks.runAsync("A", [&] {
112  ValueB = Cache.get(0, [&] {
113  ReleaseA.notify();
114  ReleaseB.wait();
115  return 'B';
116  });
117  });
118  Tasks.wait();
119 
120  ASSERT_EQ(ValueA, ValueB);
121  ASSERT_THAT(ValueA.load(), testing::AnyOf('A', 'B'));
122 }
123 
124 } // namespace clangd
125 } // namespace clang
clang::clangd::AsyncTaskRunner::wait
void wait() const
Definition: Threading.h:110
clang::clangd::Notification::wait
void wait() const
Definition: Threading.cpp:29
clang::clangd::TEST_F
TEST_F(BackgroundIndexTest, NoCrashOnErrorFile)
Definition: BackgroundIndexTests.cpp:92
clang::clangd::Notification
A threadsafe flag that is initially clear.
Definition: Threading.h:27
clang::clangd::ThreadingTest
Definition: ThreadingTests.cpp:17
Threading.h
clang::clangd::Memoize::get
Container::mapped_type get(T &&Key, Func Compute) const
Definition: Threading.h:152
clang::clangd::Notification::notify
void notify()
Definition: Threading.cpp:19
Counter
trace::Metric Counter
Definition: TraceTests.cpp:151
clang::clangd::Memoize
Memoize is a cache to store and reuse computation results based on a key.
Definition: Threading.h:144
clang::clangd::AsyncTaskRunner::runAsync
void runAsync(const llvm::Twine &Name, llvm::unique_function< void()> Action)
Definition: Threading.cpp:72
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::AsyncTaskRunner
Runs tasks on separate (detached) threads and wait for all tasks to finish.
Definition: Threading.h:105
clang::clangd::TEST_F
TEST_F(ThreadingTest, MemoizeDeterministic)
Definition: ThreadingTests.cpp:95