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