rocksdb/util/timer_test.cc
mrambacher 6924869867 Make SystemClock into a Customizable Class (#8636)
Summary:
Made SystemClock into a Customizable class, complete with CreateFromString.

Cleaned up some of the existing SystemClock implementations that were redundant (NoSleep was the same as the internal one for MockEnv).

Changed MockEnv construction to allow Clock to be passed to the Memory/MockFileSystem.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/8636

Reviewed By: zhichao-cao

Differential Revision: D30483360

Pulled By: mrambacher

fbshipit-source-id: cd0e3a876c39f8c98fe13374c06e8edbd5b9f2a1
2021-09-21 09:23:48 -07:00

402 lines
12 KiB
C++

// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "util/timer.h"
#include "db/db_test_util.h"
#include "test_util/mock_time_env.h"
namespace ROCKSDB_NAMESPACE {
class TimerTest : public testing::Test {
public:
TimerTest()
: mock_clock_(std::make_shared<MockSystemClock>(SystemClock::Default())) {
}
protected:
std::shared_ptr<MockSystemClock> mock_clock_;
void SetUp() override { mock_clock_->InstallTimedWaitFixCallback(); }
const int kUsPerSec = 1000000;
};
TEST_F(TimerTest, SingleScheduleOnce) {
const int kInitDelayUs = 1 * kUsPerSec;
Timer timer(mock_clock_.get());
int count = 0;
timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, 0);
ASSERT_TRUE(timer.Start());
ASSERT_EQ(0, count);
// Wait for execution to finish
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
ASSERT_EQ(1, count);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, MultipleScheduleOnce) {
const int kInitDelay1Us = 1 * kUsPerSec;
const int kInitDelay2Us = 3 * kUsPerSec;
Timer timer(mock_clock_.get());
int count1 = 0;
timer.Add([&] { count1++; }, "fn_sch_test1", kInitDelay1Us, 0);
int count2 = 0;
timer.Add([&] { count2++; }, "fn_sch_test2", kInitDelay2Us, 0);
ASSERT_TRUE(timer.Start());
ASSERT_EQ(0, count1);
ASSERT_EQ(0, count2);
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelay1Us); });
ASSERT_EQ(1, count1);
ASSERT_EQ(0, count2);
timer.TEST_WaitForRun([&] {
mock_clock_->SleepForMicroseconds(kInitDelay2Us - kInitDelay1Us);
});
ASSERT_EQ(1, count1);
ASSERT_EQ(1, count2);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, SingleScheduleRepeatedly) {
const int kIterations = 5;
const int kInitDelayUs = 1 * kUsPerSec;
const int kRepeatUs = 1 * kUsPerSec;
Timer timer(mock_clock_.get());
int count = 0;
timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs);
ASSERT_TRUE(timer.Start());
ASSERT_EQ(0, count);
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
ASSERT_EQ(1, count);
// Wait for execution to finish
for (int i = 1; i < kIterations; i++) {
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kRepeatUs); });
}
ASSERT_EQ(kIterations, count);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, MultipleScheduleRepeatedly) {
const int kIterations = 5;
const int kInitDelay1Us = 0 * kUsPerSec;
const int kInitDelay2Us = 1 * kUsPerSec;
const int kInitDelay3Us = 0 * kUsPerSec;
const int kRepeatUs = 2 * kUsPerSec;
const int kLargeRepeatUs = 100 * kUsPerSec;
Timer timer(mock_clock_.get());
int count1 = 0;
timer.Add([&] { count1++; }, "fn_sch_test1", kInitDelay1Us, kRepeatUs);
int count2 = 0;
timer.Add([&] { count2++; }, "fn_sch_test2", kInitDelay2Us, kRepeatUs);
// Add a function with relatively large repeat interval
int count3 = 0;
timer.Add([&] { count3++; }, "fn_sch_test3", kInitDelay3Us, kLargeRepeatUs);
ASSERT_TRUE(timer.Start());
ASSERT_EQ(0, count2);
// Wait for execution to finish
for (int i = 1; i < kIterations * (kRepeatUs / kUsPerSec); i++) {
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(1 * kUsPerSec); });
ASSERT_EQ((i + 2) / (kRepeatUs / kUsPerSec), count1);
ASSERT_EQ((i + 1) / (kRepeatUs / kUsPerSec), count2);
// large interval function should only run once (the first one).
ASSERT_EQ(1, count3);
}
timer.Cancel("fn_sch_test1");
// Wait for execution to finish
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(1 * kUsPerSec); });
ASSERT_EQ(kIterations, count1);
ASSERT_EQ(kIterations, count2);
ASSERT_EQ(1, count3);
timer.Cancel("fn_sch_test2");
ASSERT_EQ(kIterations, count1);
ASSERT_EQ(kIterations, count2);
// execute the long interval one
timer.TEST_WaitForRun([&] {
mock_clock_->SleepForMicroseconds(
kLargeRepeatUs - static_cast<int>(mock_clock_->NowMicros()));
});
ASSERT_EQ(2, count3);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, AddAfterStartTest) {
const int kIterations = 5;
const int kInitDelayUs = 1 * kUsPerSec;
const int kRepeatUs = 1 * kUsPerSec;
// wait timer to run and then add a new job
SyncPoint::GetInstance()->LoadDependency(
{{"Timer::Run::Waiting", "TimerTest:AddAfterStartTest:1"}});
SyncPoint::GetInstance()->EnableProcessing();
Timer timer(mock_clock_.get());
ASSERT_TRUE(timer.Start());
TEST_SYNC_POINT("TimerTest:AddAfterStartTest:1");
int count = 0;
timer.Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs);
ASSERT_EQ(0, count);
// Wait for execution to finish
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
ASSERT_EQ(1, count);
for (int i = 1; i < kIterations; i++) {
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kRepeatUs); });
}
ASSERT_EQ(kIterations, count);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, CancelRunningTask) {
static constexpr char kTestFuncName[] = "test_func";
const int kRepeatUs = 1 * kUsPerSec;
Timer timer(mock_clock_.get());
ASSERT_TRUE(timer.Start());
int* value = new int;
*value = 0;
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({
{"TimerTest::CancelRunningTask:test_func:0",
"TimerTest::CancelRunningTask:BeforeCancel"},
{"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting",
"TimerTest::CancelRunningTask:test_func:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
timer.Add(
[&]() {
*value = 1;
TEST_SYNC_POINT("TimerTest::CancelRunningTask:test_func:0");
TEST_SYNC_POINT("TimerTest::CancelRunningTask:test_func:1");
},
kTestFuncName, 0, kRepeatUs);
port::Thread control_thr([&]() {
TEST_SYNC_POINT("TimerTest::CancelRunningTask:BeforeCancel");
timer.Cancel(kTestFuncName);
// Verify that *value has been set to 1.
ASSERT_EQ(1, *value);
delete value;
value = nullptr;
});
mock_clock_->SleepForMicroseconds(kRepeatUs);
control_thr.join();
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, ShutdownRunningTask) {
const int kRepeatUs = 1 * kUsPerSec;
constexpr char kTestFunc1Name[] = "test_func1";
constexpr char kTestFunc2Name[] = "test_func2";
Timer timer(mock_clock_.get());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({
{"TimerTest::ShutdownRunningTest:test_func:0",
"TimerTest::ShutdownRunningTest:BeforeShutdown"},
{"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting",
"TimerTest::ShutdownRunningTest:test_func:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_TRUE(timer.Start());
int* value = new int;
*value = 0;
timer.Add(
[&]() {
TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:test_func:0");
*value = 1;
TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:test_func:1");
},
kTestFunc1Name, 0, kRepeatUs);
timer.Add([&]() { ++(*value); }, kTestFunc2Name, 0, kRepeatUs);
port::Thread control_thr([&]() {
TEST_SYNC_POINT("TimerTest::ShutdownRunningTest:BeforeShutdown");
timer.Shutdown();
});
mock_clock_->SleepForMicroseconds(kRepeatUs);
control_thr.join();
delete value;
}
TEST_F(TimerTest, AddSameFuncName) {
const int kInitDelayUs = 1 * kUsPerSec;
const int kRepeat1Us = 5 * kUsPerSec;
const int kRepeat2Us = 4 * kUsPerSec;
Timer timer(mock_clock_.get());
ASSERT_TRUE(timer.Start());
int func_counter1 = 0;
timer.Add([&] { func_counter1++; }, "duplicated_func", kInitDelayUs,
kRepeat1Us);
int func2_counter = 0;
timer.Add([&] { func2_counter++; }, "func2", kInitDelayUs, kRepeat2Us);
// New function with the same name should override the existing one
int func_counter2 = 0;
timer.Add([&] { func_counter2++; }, "duplicated_func", kInitDelayUs,
kRepeat1Us);
ASSERT_EQ(0, func_counter1);
ASSERT_EQ(0, func2_counter);
ASSERT_EQ(0, func_counter2);
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
ASSERT_EQ(0, func_counter1);
ASSERT_EQ(1, func2_counter);
ASSERT_EQ(1, func_counter2);
timer.TEST_WaitForRun([&] { mock_clock_->SleepForMicroseconds(kRepeat1Us); });
ASSERT_EQ(0, func_counter1);
ASSERT_EQ(2, func2_counter);
ASSERT_EQ(2, func_counter2);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, RepeatIntervalWithFuncRunningTime) {
const int kInitDelayUs = 1 * kUsPerSec;
const int kRepeatUs = 5 * kUsPerSec;
const int kFuncRunningTimeUs = 1 * kUsPerSec;
Timer timer(mock_clock_.get());
ASSERT_TRUE(timer.Start());
int func_counter = 0;
timer.Add(
[&] {
mock_clock_->SleepForMicroseconds(kFuncRunningTimeUs);
func_counter++;
},
"func", kInitDelayUs, kRepeatUs);
ASSERT_EQ(0, func_counter);
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
ASSERT_EQ(1, func_counter);
ASSERT_EQ(kInitDelayUs + kFuncRunningTimeUs, mock_clock_->NowMicros());
// After repeat interval time, the function is not executed, as running
// the function takes some time (`kFuncRunningTimeSec`). The repeat interval
// is the time between ending time of the last call and starting time of the
// next call.
uint64_t next_abs_interval_time_us = kInitDelayUs + kRepeatUs;
timer.TEST_WaitForRun([&] {
mock_clock_->SetCurrentTime(next_abs_interval_time_us / kUsPerSec);
});
ASSERT_EQ(1, func_counter);
// After the function running time, it's executed again
timer.TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kFuncRunningTimeUs); });
ASSERT_EQ(2, func_counter);
ASSERT_TRUE(timer.Shutdown());
}
TEST_F(TimerTest, DestroyRunningTimer) {
const int kInitDelayUs = 1 * kUsPerSec;
const int kRepeatUs = 1 * kUsPerSec;
auto timer_ptr = new Timer(mock_clock_.get());
int count = 0;
timer_ptr->Add([&] { count++; }, "fn_sch_test", kInitDelayUs, kRepeatUs);
ASSERT_TRUE(timer_ptr->Start());
timer_ptr->TEST_WaitForRun(
[&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); });
// delete a running timer should not cause any exception
delete timer_ptr;
}
TEST_F(TimerTest, DestroyTimerWithRunningFunc) {
const int kRepeatUs = 1 * kUsPerSec;
auto timer_ptr = new Timer(mock_clock_.get());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->LoadDependency({
{"TimerTest::DestroyTimerWithRunningFunc:test_func:0",
"TimerTest::DestroyTimerWithRunningFunc:BeforeDelete"},
{"Timer::WaitForTaskCompleteIfNecessary:TaskExecuting",
"TimerTest::DestroyTimerWithRunningFunc:test_func:1"},
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_TRUE(timer_ptr->Start());
int count = 0;
timer_ptr->Add(
[&]() {
TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:test_func:0");
count++;
TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:test_func:1");
},
"fn_running_test", 0, kRepeatUs);
port::Thread control_thr([&] {
TEST_SYNC_POINT("TimerTest::DestroyTimerWithRunningFunc:BeforeDelete");
delete timer_ptr;
});
mock_clock_->SleepForMicroseconds(kRepeatUs);
control_thr.join();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}