proxygen
MutexTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <folly/Portability.h>
18 
19 #if FOLLY_HAS_COROUTINES
20 
30 
31 #include <mutex>
32 
33 using namespace folly;
34 
35 TEST(Mutex, TryLock) {
36  coro::Mutex m;
37  CHECK(m.try_lock());
38  CHECK(!m.try_lock());
39  m.unlock();
40  CHECK(m.try_lock());
41 }
42 
43 TEST(Mutex, ScopedLock) {
44  coro::Mutex m;
45  {
46  std::unique_lock<coro::Mutex> lock{m, std::try_to_lock};
47  CHECK(lock.owns_lock());
48 
49  {
50  std::unique_lock<coro::Mutex> lock2{m, std::try_to_lock};
51  CHECK(!lock2.owns_lock());
52  }
53  }
54 
55  CHECK(m.try_lock());
56  m.unlock();
57 }
58 
59 TEST(Mutex, LockAsync) {
60  coro::Mutex m;
61  coro::Baton b1;
62  coro::Baton b2;
63 
64  int value = 0;
65 
66  auto makeTask = [&](coro::Baton& b) -> coro::Task<void> {
67  co_await m.co_lock();
68  ++value;
69  co_await b;
70  ++value;
71  m.unlock();
72  };
73 
74  auto& inlineExecutor = InlineExecutor::instance();
75 
76  auto f1 = makeTask(b1).scheduleOn(&inlineExecutor).start();
77  CHECK_EQ(1, value);
78  CHECK(!m.try_lock());
79 
80  auto f2 = makeTask(b2).scheduleOn(&inlineExecutor).start();
81  CHECK_EQ(1, value);
82 
83  // This will resume f1 coroutine and let it release the
84  // lock. This will in turn resume f2 which was suspended
85  // at co_await m.lockAsync() which will then increment the value
86  // before becoming blocked on
87  b1.post();
88 
89  CHECK_EQ(3, value);
90  CHECK(!m.try_lock());
91 
92  b2.post();
93  CHECK_EQ(4, value);
94  CHECK(m.try_lock());
95 }
96 
97 TEST(Mutex, ScopedLockAsync) {
98  coro::Mutex m;
99  coro::Baton b1;
100  coro::Baton b2;
101 
102  int value = 0;
103 
104  auto makeTask = [&](coro::Baton& b) -> coro::Task<void> {
105  auto lock = co_await m.co_scoped_lock();
106  ++value;
107  co_await b;
108  ++value;
109  };
110 
111  auto& inlineExecutor = InlineExecutor::instance();
112 
113  auto f1 = makeTask(b1).scheduleOn(&inlineExecutor).start();
114  CHECK_EQ(1, value);
115  CHECK(!m.try_lock());
116 
117  auto f2 = makeTask(b2).scheduleOn(&inlineExecutor).start();
118  CHECK_EQ(1, value);
119 
120  // This will resume f1 coroutine and let it release the
121  // lock. This will in turn resume f2 which was suspended
122  // at co_await m.lockAsync() which will then increment the value
123  // before becoming blocked on b2.
124  b1.post();
125 
126  CHECK_EQ(3, value);
127  CHECK(!m.try_lock());
128 
129  b2.post();
130  CHECK_EQ(4, value);
131  CHECK(m.try_lock());
132 }
133 
134 TEST(Mutex, ThreadSafety) {
135  CPUThreadPoolExecutor threadPool{
136  2, std::make_shared<NamedThreadFactory>("CPUThreadPool")};
137 
138  int value = 0;
140 
141  auto makeTask = [&]() -> coro::Task<void> {
142  for (int i = 0; i < 10'000; ++i) {
143  auto lock = co_await mutex.co_scoped_lock();
144  ++value;
145  }
146  };
147 
148  auto f1 = makeTask().scheduleOn(&threadPool).start();
149  auto f2 = makeTask().scheduleOn(&threadPool).start();
150  auto f3 = makeTask().scheduleOn(&threadPool).start();
151 
152  std::move(f1).get();
153  std::move(f2).get();
154  std::move(f3).get();
155 
156  CHECK_EQ(30'000, value);
157 }
158 
159 TEST(Mutex, InlineTaskDeadlock) {
160  coro::Mutex coroMutex;
161  std::timed_mutex stdMutex;
162 
163  std::thread thread1([&] {
165  [](auto& coroMutex, auto& stdMutex) -> coro::detail::InlineTask<void> {
166  co_await coroMutex.co_lock();
167  std::this_thread::sleep_for(std::chrono::milliseconds{200});
168  stdMutex.lock();
169  // At this point the other coroutine is suspended waiting on
170  // coroMutex.co_lock(). coroMutex.unlock() will unlock the mutex and
171  // run the other coroutine *inline*. That coroutine will
172  // try to acquire stdMutex resulting in a deadlock.
173  coroMutex.unlock();
174  stdMutex.unlock();
175  }(coroMutex, stdMutex));
176  });
177 
178  std::thread thread2([&] {
180  [](auto& coroMutex, auto& stdMutex) -> coro::detail::InlineTask<void> {
181  std::this_thread::sleep_for(std::chrono::milliseconds{100});
182  co_await coroMutex.co_lock();
183  EXPECT_FALSE(stdMutex.try_lock_for(std::chrono::milliseconds{500}));
184  coroMutex.unlock();
185  }(coroMutex, stdMutex));
186  });
187 
188  thread1.join();
189  thread2.join();
190 }
191 
192 #endif
LockOperation co_lock() noexcept
Definition: Mutex.h:221
char b
#define Mutex
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
void post() noexcept
bool try_lock() noexcept
Definition: Mutex.h:106
FOLLY_ATTR_VISIBILITY_HIDDEN static FOLLY_ALWAYS_INLINE InlineExecutor & instance() noexcept
auto lock(Synchronized< D, M > &synchronized, Args &&...args)
static map< string, int > m
void unlock() noexcept
std::mutex mutex
ScopedLockOperation co_scoped_lock() noexcept
Definition: Mutex.h:217
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
#define EXPECT_FALSE(condition)
Definition: gtest.h:1862
TEST(SequencedExecutor, CPUThreadPoolExecutor)
auto blockingWait(Awaitable &&awaitable) -> detail::decay_rvalue_reference_t< await_result_t< Awaitable >>
Definition: BlockingWait.h:313