From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- src/rocksdb/util/timer_test.cc | 402 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 src/rocksdb/util/timer_test.cc (limited to 'src/rocksdb/util/timer_test.cc') diff --git a/src/rocksdb/util/timer_test.cc b/src/rocksdb/util/timer_test.cc new file mode 100644 index 000000000..0ebfa9f3d --- /dev/null +++ b/src/rocksdb/util/timer_test.cc @@ -0,0 +1,402 @@ +// 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 "rocksdb/file_system.h" +#include "test_util/mock_time_env.h" + +namespace ROCKSDB_NAMESPACE { + +class TimerTest : public testing::Test { + public: + TimerTest() + : mock_clock_(std::make_shared(SystemClock::Default())) { + } + + protected: + std::shared_ptr 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(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; + ASSERT_TRUE(timer.Add([&] { func_counter1++; }, "duplicated_func", + kInitDelayUs, kRepeat1Us)); + + int func2_counter = 0; + ASSERT_TRUE( + timer.Add([&] { func2_counter++; }, "func2", kInitDelayUs, kRepeat2Us)); + + // New function with the same name should fail to add + int func_counter2 = 0; + ASSERT_FALSE(timer.Add([&] { func_counter2++; }, "duplicated_func", + kInitDelayUs, kRepeat1Us)); + + ASSERT_EQ(0, func_counter1); + ASSERT_EQ(0, func2_counter); + + timer.TEST_WaitForRun( + [&] { mock_clock_->SleepForMicroseconds(kInitDelayUs); }); + + ASSERT_EQ(1, func_counter1); + ASSERT_EQ(1, func2_counter); + + timer.TEST_WaitForRun([&] { mock_clock_->SleepForMicroseconds(kRepeat1Us); }); + + ASSERT_EQ(2, func_counter1); + ASSERT_EQ(2, func2_counter); + ASSERT_EQ(0, 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) { + ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} -- cgit v1.2.3