1 // Copyright 2017 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "absl/synchronization/internal/per_thread_sem.h"
16
17 #include <atomic>
18 #include <condition_variable> // NOLINT(build/c++11)
19 #include <functional>
20 #include <limits>
21 #include <mutex> // NOLINT(build/c++11)
22 #include <string>
23 #include <thread> // NOLINT(build/c++11)
24
25 #include "gtest/gtest.h"
26 #include "absl/base/internal/cycleclock.h"
27 #include "absl/base/internal/thread_identity.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/time/clock.h"
30 #include "absl/time/time.h"
31
32 // In this test we explicitly avoid the use of synchronization
33 // primitives which might use PerThreadSem, most notably absl::Mutex.
34
35 namespace absl {
36 ABSL_NAMESPACE_BEGIN
37 namespace synchronization_internal {
38
39 class SimpleSemaphore {
40 public:
SimpleSemaphore()41 SimpleSemaphore() : count_(0) {}
42
43 // Decrements (locks) the semaphore. If the semaphore's value is
44 // greater than zero, then the decrement proceeds, and the function
45 // returns, immediately. If the semaphore currently has the value
46 // zero, then the call blocks until it becomes possible to perform
47 // the decrement.
Wait()48 void Wait() {
49 std::unique_lock<std::mutex> lock(mu_);
50 cv_.wait(lock, [this]() { return count_ > 0; });
51 --count_;
52 cv_.notify_one();
53 }
54
55 // Increments (unlocks) the semaphore. If the semaphore's value
56 // consequently becomes greater than zero, then another thread
57 // blocked Wait() call will be woken up and proceed to lock the
58 // semaphore.
Post()59 void Post() {
60 std::lock_guard<std::mutex> lock(mu_);
61 ++count_;
62 cv_.notify_one();
63 }
64
65 private:
66 std::mutex mu_;
67 std::condition_variable cv_;
68 int count_;
69 };
70
71 struct ThreadData {
72 int num_iterations; // Number of replies to send.
73 SimpleSemaphore identity2_written; // Posted by thread writing identity2.
74 base_internal::ThreadIdentity *identity1; // First Post()-er.
75 base_internal::ThreadIdentity *identity2; // First Wait()-er.
76 KernelTimeout timeout;
77 };
78
79 // Need friendship with PerThreadSem.
80 class PerThreadSemTest : public testing::Test {
81 public:
TimingThread(ThreadData * t)82 static void TimingThread(ThreadData* t) {
83 t->identity2 = GetOrCreateCurrentThreadIdentity();
84 t->identity2_written.Post();
85 while (t->num_iterations--) {
86 Wait(t->timeout);
87 Post(t->identity1);
88 }
89 }
90
TestTiming(const char * msg,bool timeout)91 void TestTiming(const char *msg, bool timeout) {
92 static const int kNumIterations = 100;
93 ThreadData t;
94 t.num_iterations = kNumIterations;
95 t.timeout = timeout ?
96 KernelTimeout(absl::Now() + absl::Seconds(10000)) // far in the future
97 : KernelTimeout::Never();
98 t.identity1 = GetOrCreateCurrentThreadIdentity();
99
100 // We can't use the Thread class here because it uses the Mutex
101 // class which will invoke PerThreadSem, so we use std::thread instead.
102 std::thread partner_thread(std::bind(TimingThread, &t));
103
104 // Wait for our partner thread to register their identity.
105 t.identity2_written.Wait();
106
107 int64_t min_cycles = std::numeric_limits<int64_t>::max();
108 int64_t total_cycles = 0;
109 for (int i = 0; i < kNumIterations; ++i) {
110 absl::SleepFor(absl::Milliseconds(20));
111 int64_t cycles = base_internal::CycleClock::Now();
112 Post(t.identity2);
113 Wait(t.timeout);
114 cycles = base_internal::CycleClock::Now() - cycles;
115 min_cycles = std::min(min_cycles, cycles);
116 total_cycles += cycles;
117 }
118 std::string out = StrCat(
119 msg, "min cycle count=", min_cycles, " avg cycle count=",
120 absl::SixDigits(static_cast<double>(total_cycles) / kNumIterations));
121 printf("%s\n", out.c_str());
122
123 partner_thread.join();
124 }
125
126 protected:
Post(base_internal::ThreadIdentity * id)127 static void Post(base_internal::ThreadIdentity *id) {
128 PerThreadSem::Post(id);
129 }
Wait(KernelTimeout t)130 static bool Wait(KernelTimeout t) {
131 return PerThreadSem::Wait(t);
132 }
133
134 // convenience overload
Wait(absl::Time t)135 static bool Wait(absl::Time t) {
136 return Wait(KernelTimeout(t));
137 }
138
Tick(base_internal::ThreadIdentity * identity)139 static void Tick(base_internal::ThreadIdentity *identity) {
140 PerThreadSem::Tick(identity);
141 }
142 };
143
144 namespace {
145
TEST_F(PerThreadSemTest,WithoutTimeout)146 TEST_F(PerThreadSemTest, WithoutTimeout) {
147 PerThreadSemTest::TestTiming("Without timeout: ", false);
148 }
149
TEST_F(PerThreadSemTest,WithTimeout)150 TEST_F(PerThreadSemTest, WithTimeout) {
151 PerThreadSemTest::TestTiming("With timeout: ", true);
152 }
153
TEST_F(PerThreadSemTest,Timeouts)154 TEST_F(PerThreadSemTest, Timeouts) {
155 const absl::Duration delay = absl::Milliseconds(50);
156 const absl::Time start = absl::Now();
157 EXPECT_FALSE(Wait(start + delay));
158 const absl::Duration elapsed = absl::Now() - start;
159 // Allow for a slight early return, to account for quality of implementation
160 // issues on various platforms.
161 const absl::Duration slop = absl::Microseconds(200);
162 EXPECT_LE(delay - slop, elapsed)
163 << "Wait returned " << delay - elapsed
164 << " early (with " << slop << " slop), start time was " << start;
165
166 absl::Time negative_timeout = absl::UnixEpoch() - absl::Milliseconds(100);
167 EXPECT_FALSE(Wait(negative_timeout));
168 EXPECT_LE(negative_timeout, absl::Now() + slop); // trivially true :)
169
170 Post(GetOrCreateCurrentThreadIdentity());
171 // The wait here has an expired timeout, but we have a wake to consume,
172 // so this should succeed
173 EXPECT_TRUE(Wait(negative_timeout));
174 }
175
176 } // namespace
177
178 } // namespace synchronization_internal
179 ABSL_NAMESPACE_END
180 } // namespace absl
181