1 //===----------------------------------------------------------------------===//
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 // UNSUPPORTED: no-threads
10 // UNSUPPORTED: c++03, c++11
11 
12 // <shared_mutex>
13 
14 // class shared_timed_mutex;
15 
16 // template <class Clock, class Duration>
17 //     bool try_lock_shared_until(const chrono::time_point<Clock, Duration>& abs_time);
18 
19 #include <shared_mutex>
20 #include <atomic>
21 #include <cassert>
22 #include <chrono>
23 #include <thread>
24 #include <vector>
25 
26 #include "make_test_thread.h"
27 
28 template <class Function>
29 std::chrono::microseconds measure(Function f) {
30   std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
31   f();
32   std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
33   return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
34 }
35 
36 int main(int, char**) {
37   // Try to lock-shared a mutex that is not locked yet. This should succeed immediately.
38   {
39     std::shared_timed_mutex m;
40     std::vector<std::thread> threads;
41     for (int i = 0; i != 5; ++i) {
42       threads.push_back(support::make_test_thread([&] {
43         bool succeeded = m.try_lock_shared_until(std::chrono::steady_clock::now() + std::chrono::milliseconds(1));
44         assert(succeeded);
45         m.unlock_shared();
46       }));
47     }
48 
49     for (auto& t : threads)
50       t.join();
51   }
52 
53   // Try to lock-shared an already-locked mutex for a long enough amount of time and succeed.
54   // This is technically flaky, but we use such long durations that it should pass even
55   // in slow or contended environments.
56   {
57     std::chrono::milliseconds const wait_time(500);
58     std::chrono::milliseconds const tolerance = wait_time * 3;
59     std::atomic<int> ready(0);
60 
61     std::shared_timed_mutex m;
62     m.lock();
63 
64     std::vector<std::thread> threads;
65     for (int i = 0; i != 5; ++i) {
66       threads.push_back(support::make_test_thread([&] {
67         ++ready;
68         while (ready < 5)
69           /* spin until all threads are created */;
70 
71         auto elapsed = measure([&] {
72           bool succeeded = m.try_lock_shared_until(std::chrono::steady_clock::now() + wait_time);
73           assert(succeeded);
74           m.unlock_shared();
75         });
76 
77         // Ensure we didn't wait significantly longer than our timeout. This is technically
78         // flaky and non-conforming because an implementation is free to block for arbitrarily
79         // long, but any decent quality implementation should pass this test.
80         assert(elapsed - wait_time < tolerance);
81       }));
82     }
83 
84     // Wait for all the threads to be ready to take the lock before we unlock it from here, otherwise
85     // there's a high chance that we're not testing the "locking an already locked" mutex use case.
86     // There is still technically a race condition here.
87     while (ready < 5)
88       /* spin */;
89     std::this_thread::sleep_for(wait_time / 5);
90 
91     m.unlock(); // this should allow the threads to lock-shared 'm'
92 
93     for (auto& t : threads)
94       t.join();
95   }
96 
97   // Try to lock-shared an already-locked mutex for a short amount of time and fail.
98   // Again, this is technically flaky but we use such long durations that it should work.
99   {
100     std::chrono::milliseconds const wait_time(10);
101     std::chrono::milliseconds const tolerance(750); // in case the thread we spawned goes to sleep or something
102 
103     std::shared_timed_mutex m;
104     m.lock();
105 
106     std::vector<std::thread> threads;
107     for (int i = 0; i != 5; ++i) {
108       threads.push_back(support::make_test_thread([&] {
109         auto elapsed = measure([&] {
110           bool succeeded = m.try_lock_shared_until(std::chrono::steady_clock::now() + wait_time);
111           assert(!succeeded);
112         });
113 
114         // Ensure we failed within some bounded time.
115         assert(elapsed - wait_time < tolerance);
116       }));
117     }
118 
119     for (auto& t : threads)
120       t.join();
121 
122     m.unlock();
123   }
124 
125   return 0;
126 }
127