1ad97ccf6SSam McCall //===-- ThreadingTests.cpp --------------------------------------*- C++ -*-===//
2ad97ccf6SSam McCall //
3ad97ccf6SSam McCall // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4ad97ccf6SSam McCall // See https://llvm.org/LICENSE.txt for license information.
5ad97ccf6SSam McCall // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6ad97ccf6SSam McCall //
7ad97ccf6SSam McCall //===----------------------------------------------------------------------===//
8ad97ccf6SSam McCall
9ad97ccf6SSam McCall #include "support/Threading.h"
102a3ac01bSSam McCall #include "llvm/ADT/DenseMap.h"
112a3ac01bSSam McCall #include "gmock/gmock.h"
12ad97ccf6SSam McCall #include "gtest/gtest.h"
133dbe471aSSam McCall #include <chrono>
14ad97ccf6SSam McCall #include <mutex>
15ad97ccf6SSam McCall
16ad97ccf6SSam McCall namespace clang {
17ad97ccf6SSam McCall namespace clangd {
18ad97ccf6SSam McCall class ThreadingTest : public ::testing::Test {};
19ad97ccf6SSam McCall
TEST_F(ThreadingTest,TaskRunner)20ad97ccf6SSam McCall TEST_F(ThreadingTest, TaskRunner) {
21ad97ccf6SSam McCall const int TasksCnt = 100;
22ad97ccf6SSam McCall // This should be const, but MSVC does not allow to use const vars in lambdas
23ad97ccf6SSam McCall // without capture. On the other hand, clang gives a warning that capture of
24ad97ccf6SSam McCall // const var is not required.
25ad97ccf6SSam McCall // Making it non-const makes both compilers happy.
26ad97ccf6SSam McCall int IncrementsPerTask = 1000;
27ad97ccf6SSam McCall
28ad97ccf6SSam McCall std::mutex Mutex;
29ad97ccf6SSam McCall int Counter(0); /* GUARDED_BY(Mutex) */
30ad97ccf6SSam McCall {
31ad97ccf6SSam McCall AsyncTaskRunner Tasks;
32*8edfc2f8SChristian Kühnel auto ScheduleIncrements = [&]() {
33ad97ccf6SSam McCall for (int TaskI = 0; TaskI < TasksCnt; ++TaskI) {
34ad97ccf6SSam McCall Tasks.runAsync("task", [&Counter, &Mutex, IncrementsPerTask]() {
35ad97ccf6SSam McCall for (int Increment = 0; Increment < IncrementsPerTask; ++Increment) {
36ad97ccf6SSam McCall std::lock_guard<std::mutex> Lock(Mutex);
37ad97ccf6SSam McCall ++Counter;
38ad97ccf6SSam McCall }
39ad97ccf6SSam McCall });
40ad97ccf6SSam McCall }
41ad97ccf6SSam McCall };
42ad97ccf6SSam McCall
43ad97ccf6SSam McCall {
44ad97ccf6SSam McCall // Make sure runAsync is not running tasks synchronously on the same
45ad97ccf6SSam McCall // thread by locking the Mutex used for increments.
46ad97ccf6SSam McCall std::lock_guard<std::mutex> Lock(Mutex);
47*8edfc2f8SChristian Kühnel ScheduleIncrements();
48ad97ccf6SSam McCall }
49ad97ccf6SSam McCall
50ad97ccf6SSam McCall Tasks.wait();
51ad97ccf6SSam McCall {
52ad97ccf6SSam McCall std::lock_guard<std::mutex> Lock(Mutex);
53ad97ccf6SSam McCall ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask);
54ad97ccf6SSam McCall }
55ad97ccf6SSam McCall
56ad97ccf6SSam McCall {
57ad97ccf6SSam McCall std::lock_guard<std::mutex> Lock(Mutex);
58ad97ccf6SSam McCall Counter = 0;
59*8edfc2f8SChristian Kühnel ScheduleIncrements();
60ad97ccf6SSam McCall }
61ad97ccf6SSam McCall }
62ad97ccf6SSam McCall // Check that destructor has waited for tasks to finish.
63ad97ccf6SSam McCall std::lock_guard<std::mutex> Lock(Mutex);
64ad97ccf6SSam McCall ASSERT_EQ(Counter, TasksCnt * IncrementsPerTask);
65ad97ccf6SSam McCall }
662a3ac01bSSam McCall
TEST_F(ThreadingTest,Memoize)672a3ac01bSSam McCall TEST_F(ThreadingTest, Memoize) {
682a3ac01bSSam McCall const unsigned NumThreads = 5;
692a3ac01bSSam McCall const unsigned NumKeys = 100;
702a3ac01bSSam McCall const unsigned NumIterations = 100;
712a3ac01bSSam McCall
722a3ac01bSSam McCall Memoize<llvm::DenseMap<int, int>> Cache;
732a3ac01bSSam McCall std::atomic<unsigned> ComputeCount(0);
742a3ac01bSSam McCall std::atomic<int> ComputeResult[NumKeys];
752a3ac01bSSam McCall std::fill(std::begin(ComputeResult), std::end(ComputeResult), -1);
762a3ac01bSSam McCall
772a3ac01bSSam McCall AsyncTaskRunner Tasks;
782a3ac01bSSam McCall for (unsigned I = 0; I < NumThreads; ++I)
792a3ac01bSSam McCall Tasks.runAsync("worker" + std::to_string(I), [&] {
802a3ac01bSSam McCall for (unsigned J = 0; J < NumIterations; J++)
812a3ac01bSSam McCall for (unsigned K = 0; K < NumKeys; K++) {
822a3ac01bSSam McCall int Result = Cache.get(K, [&] { return ++ComputeCount; });
832a3ac01bSSam McCall EXPECT_THAT(ComputeResult[K].exchange(Result),
842a3ac01bSSam McCall testing::AnyOf(-1, Result))
852a3ac01bSSam McCall << "Got inconsistent results from memoize";
862a3ac01bSSam McCall }
872a3ac01bSSam McCall });
882a3ac01bSSam McCall Tasks.wait();
892a3ac01bSSam McCall EXPECT_GE(ComputeCount, NumKeys) << "Computed each key once";
902a3ac01bSSam McCall EXPECT_LE(ComputeCount, NumThreads * NumKeys)
912a3ac01bSSam McCall << "Worst case, computed each key in every thread";
922a3ac01bSSam McCall for (int Result : ComputeResult)
932a3ac01bSSam McCall EXPECT_GT(Result, 0) << "All results in expected domain";
942a3ac01bSSam McCall }
952a3ac01bSSam McCall
TEST_F(ThreadingTest,MemoizeDeterministic)962a3ac01bSSam McCall TEST_F(ThreadingTest, MemoizeDeterministic) {
972a3ac01bSSam McCall Memoize<llvm::DenseMap<int, char>> Cache;
982a3ac01bSSam McCall
992a3ac01bSSam McCall // Spawn two parallel computations, A and B.
1002a3ac01bSSam McCall // Force concurrency: neither can finish until both have started.
1012a3ac01bSSam McCall // Verify that cache returns consistent results.
1022a3ac01bSSam McCall AsyncTaskRunner Tasks;
1032a3ac01bSSam McCall std::atomic<char> ValueA(0), ValueB(0);
1042a3ac01bSSam McCall Notification ReleaseA, ReleaseB;
1052a3ac01bSSam McCall Tasks.runAsync("A", [&] {
1062a3ac01bSSam McCall ValueA = Cache.get(0, [&] {
1072a3ac01bSSam McCall ReleaseB.notify();
1082a3ac01bSSam McCall ReleaseA.wait();
1092a3ac01bSSam McCall return 'A';
1102a3ac01bSSam McCall });
1112a3ac01bSSam McCall });
1122a3ac01bSSam McCall Tasks.runAsync("A", [&] {
1132a3ac01bSSam McCall ValueB = Cache.get(0, [&] {
1142a3ac01bSSam McCall ReleaseA.notify();
1152a3ac01bSSam McCall ReleaseB.wait();
1162a3ac01bSSam McCall return 'B';
1172a3ac01bSSam McCall });
1182a3ac01bSSam McCall });
1192a3ac01bSSam McCall Tasks.wait();
1202a3ac01bSSam McCall
1212a3ac01bSSam McCall ASSERT_EQ(ValueA, ValueB);
1222a3ac01bSSam McCall ASSERT_THAT(ValueA.load(), testing::AnyOf('A', 'B'));
1232a3ac01bSSam McCall }
1242a3ac01bSSam McCall
1253dbe471aSSam McCall // It's hard to write a real test of this class, std::chrono is awkward to mock.
1263dbe471aSSam McCall // But test some degenerate cases at least.
TEST(PeriodicThrottlerTest,Minimal)1273dbe471aSSam McCall TEST(PeriodicThrottlerTest, Minimal) {
1283dbe471aSSam McCall PeriodicThrottler Once(std::chrono::hours(24));
1293dbe471aSSam McCall EXPECT_TRUE(Once());
1303dbe471aSSam McCall EXPECT_FALSE(Once());
1313dbe471aSSam McCall EXPECT_FALSE(Once());
1323dbe471aSSam McCall
1333dbe471aSSam McCall PeriodicThrottler Later(std::chrono::hours(24),
1343dbe471aSSam McCall /*Delay=*/std::chrono::hours(24));
1353dbe471aSSam McCall EXPECT_FALSE(Later());
1363dbe471aSSam McCall EXPECT_FALSE(Later());
1373dbe471aSSam McCall EXPECT_FALSE(Later());
1383dbe471aSSam McCall
1393dbe471aSSam McCall PeriodicThrottler Always(std::chrono::seconds(0));
1403dbe471aSSam McCall EXPECT_TRUE(Always());
1413dbe471aSSam McCall EXPECT_TRUE(Always());
1423dbe471aSSam McCall EXPECT_TRUE(Always());
1433dbe471aSSam McCall }
1443dbe471aSSam McCall
145ad97ccf6SSam McCall } // namespace clangd
146ad97ccf6SSam McCall } // namespace clang
147