Skip to content

Commit 035ad2a

Browse files
committed
fix deadlock in RCU retire when rcu_reader active
Summary: Every 3.2 seconds RCU retire tries to opportunistically drain the queue of pending work. If this happens while the current thread is holding an rcu_reader and another thread is currently running synchronize(), a deadlock will occur. This diff adds a unit test for the behavior and fixes the problem. Test Plan: * unit test that reproduces the problem * unit test passes with the fix
1 parent 535a8ed commit 035ad2a

File tree

3 files changed

+30
-2
lines changed

3 files changed

+30
-2
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,7 @@ if (BUILD_TESTS)
822822
TEST baton_test SOURCES BatonTest.cpp
823823
TEST call_once_test SOURCES CallOnceTest.cpp
824824
TEST lifo_sem_test SOURCES LifoSemTests.cpp
825+
TEST rcu_test SOURCES RcuTest.cpp
825826
TEST rw_spin_lock_test SOURCES RWSpinLockTest.cpp
826827
TEST semaphore_test SOURCES SemaphoreTest.cpp
827828

folly/synchronization/Rcu-inl.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,12 @@ void rcu_domain<Tag>::retire(list_node* node) noexcept {
9191
syncTime, time, std::memory_order_relaxed)) {
9292
list_head finished;
9393
{
94-
std::lock_guard<std::mutex> g(syncMutex_);
95-
half_sync(false, finished);
94+
// synchronize() blocks while holding syncMutex_,
95+
// so we must not wait for syncMutex_
96+
std::unique_lock<std::mutex> g(syncMutex_, std::try_to_lock);
97+
if (g) {
98+
half_sync(false, finished);
99+
}
96100
}
97101
// callbacks are called outside of syncMutex_
98102
finished.forEach(

folly/synchronization/test/RcuTest.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,26 @@ TEST(RcuTest, Tsan) {
308308
t2.join();
309309
EXPECT_EQ(data, 2);
310310
}
311+
312+
TEST(RcuTest, RetireUnderLock) {
313+
using namespace std::chrono;
314+
315+
// syncTimePeriod is 3.2s
316+
auto deadline = steady_clock::now() + seconds(4);
317+
318+
std::thread t1([&] {
319+
while (steady_clock::now() < deadline) {
320+
synchronize_rcu();
321+
}
322+
});
323+
324+
std::thread t2([&] {
325+
while (steady_clock::now() < deadline) {
326+
rcu_reader g;
327+
rcu_default_domain()->call([] {});
328+
}
329+
});
330+
331+
t1.join();
332+
t2.join();
333+
}

0 commit comments

Comments
 (0)