Skip to content

Commit bb54245

Browse files
committed
repro test case
1 parent 30bf39e commit bb54245

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

db/db_test_util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,10 @@ class SpecialEnv : public EnvWrapper {
579579
// To prevent slowdown, this `TimedWait()` is completely synthetic. The mock
580580
// implementation here pretends the timeout always happened.
581581
cv->GetMutex()->Unlock();
582+
TEST_SYNC_POINT("SpecialEnv::TimedWait:UnlockedPreSleep");
582583
addon_microseconds_.fetch_add(static_cast<int64_t>(delay_micros));
584+
TEST_SYNC_POINT("SpecialEnv::TimedWait:UnlockedPostSleep1");
585+
TEST_SYNC_POINT("SpecialEnv::TimedWait:UnlockedPostSleep2");
583586
cv->GetMutex()->Lock();
584587
return true;
585588
}

util/rate_limiter_test.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,63 @@ TEST_F(RateLimiterTest, AutoTuneIncreaseWhenFull) {
497497
ASSERT_LT(new_bytes_per_sec, orig_bytes_per_sec);
498498
}
499499

500+
TEST_F(RateLimiterTest, WaitHangingBug) {
501+
// At t=0: Threads 0 and 1 request `kBytesPerRefill` bytes at low-pri. One
502+
// will be granted immediately and the other will enter `TimedWait()`.
503+
//
504+
// At t=`kMicrosPerRefill`: Thread 2 requests `kBytesPerRefill` bytes at
505+
// low-pri. Thread 2's request enters the queue. To expose the bug scenario,
506+
// `SyncPoint`s ensure this happens while the lock is temporarily released in
507+
// `TimedWait()`. Before the bug fix, Thread 2's request would then hang in
508+
// `Wait()` interminably.
509+
const int kBytesPerSecond = 100;
510+
const int kMicrosPerSecond = 1000 * 1000;
511+
const int kMicrosPerRefill = kMicrosPerSecond;
512+
const int kBytesPerRefill =
513+
kBytesPerSecond * kMicrosPerRefill / kMicrosPerSecond;
514+
515+
SpecialEnv special_env(Env::Default(), true /* time_elapse_only_sleep */);
516+
std::unique_ptr<RateLimiter> limiter(new GenericRateLimiter(
517+
kBytesPerSecond, kMicrosPerRefill, 10 /* fairness */,
518+
RateLimiter::Mode::kWritesOnly, special_env.GetSystemClock(),
519+
false /* auto_tuned */));
520+
std::array<std::thread, 3> request_threads;
521+
522+
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
523+
{{"RateLimiterTest::WaitHangingBug:InitialRequestsReady",
524+
"SpecialEnv::TimedWait:UnlockedPreSleep"},
525+
{"SpecialEnv::TimedWait:UnlockedPostSleep1",
526+
"RateLimiterTest::WaitHangingBug:TestThreadRequestBegin"},
527+
{"RateLimiterTest::WaitHangingBug:TestThreadRequestEnd",
528+
"SpecialEnv::TimedWait:UnlockedPostSleep2"}});
529+
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
530+
531+
for (int i = 0; i < 2; i++) {
532+
request_threads[i] = std::thread([&]() {
533+
limiter->Request(kBytesPerRefill /* bytes */, Env::IOPriority::IO_LOW,
534+
nullptr /* stats */, RateLimiter::OpType::kWrite,
535+
&special_env);
536+
});
537+
}
538+
while (limiter->GetTotalRequests() < 2) {
539+
}
540+
TEST_SYNC_POINT("RateLimiterTest::WaitHangingBug:InitialRequestsReady");
541+
542+
TEST_SYNC_POINT("RateLimiterTest::WaitHangingBug:TestThreadRequestBegin");
543+
request_threads[2] = std::thread([&]() {
544+
limiter->Request(kBytesPerRefill /* bytes */, Env::IOPriority::IO_LOW,
545+
nullptr /* stats */, RateLimiter::OpType::kWrite,
546+
&special_env);
547+
});
548+
while (limiter->GetTotalRequests() < 3) {
549+
}
550+
TEST_SYNC_POINT("RateLimiterTest::WaitHangingBug:TestThreadRequestEnd");
551+
552+
for (int i = 0; i < 3; i++) {
553+
request_threads[i].join();
554+
}
555+
}
556+
500557
} // namespace ROCKSDB_NAMESPACE
501558

502559
int main(int argc, char** argv) {

0 commit comments

Comments
 (0)