diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp index 01f9a0d4436..7d10da2874d 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp +++ b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.cpp @@ -8,215 +8,45 @@ #include #include "CompilerConstants.hpp" +#include "GCSchedulerImpl.hpp" #include "GlobalData.hpp" #include "KAssert.h" #include "Porting.h" #include "ThreadRegistry.hpp" #include "ThreadData.hpp" -#ifndef KONAN_NO_THREADS -#include "RepeatedTimer.hpp" -#endif - using namespace kotlin; namespace { -class HeapGrowthController { -public: - explicit HeapGrowthController(gc::GCSchedulerConfig& config) noexcept : config_(config) {} - - // Called by the mutators. - void OnAllocated(size_t allocatedBytes) noexcept { allocatedBytes_ += allocatedBytes; } - - // Called by the GC thread. - void OnPerformFullGC() noexcept { allocatedBytes_ = 0; } - - // Called by the GC thread. - void UpdateAliveSetBytes(size_t bytes) noexcept { - lastAliveSetBytes_ = bytes; - - if (config_.autoTune.load()) { - double targetHeapBytes = static_cast(bytes) / config_.targetHeapUtilization; - if (!std::isfinite(targetHeapBytes)) { - // This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything. - return; - } - double minHeapBytes = static_cast(config_.minHeapBytes.load()); - double maxHeapBytes = static_cast(config_.maxHeapBytes.load()); - targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes); - config_.targetHeapBytes = static_cast(targetHeapBytes); - } - } - - // Called by the mutators. - bool NeedsGC() const noexcept { - uint64_t currentHeapBytes = allocatedBytes_.load() + lastAliveSetBytes_.load(); - uint64_t targetHeapBytes = config_.targetHeapBytes; - return currentHeapBytes >= targetHeapBytes; - } - -private: - gc::GCSchedulerConfig& config_; - // Updated by both the mutators and the GC thread. - std::atomic allocatedBytes_ = 0; - // Updated by the GC thread, read by the mutators. - std::atomic lastAliveSetBytes_ = 0; -}; - -class RegularIntervalPacer { -public: - using TimePoint = std::chrono::time_point; - using CurrentTimeProvider = std::function; - - RegularIntervalPacer(gc::GCSchedulerConfig& config, CurrentTimeProvider currentTimeProvider) noexcept : - config_(config), currentTimeProvider_(currentTimeProvider), lastGC_(currentTimeProvider_()) {} - - // Called by the mutators or the timer thread. - bool NeedsGC() const noexcept { - auto currentTimeProvider = currentTimeProvider_(); - return currentTimeProvider >= lastGC_.load() + config_.regularGcInterval(); - } - - // Called by the GC thread. - void OnPerformFullGC() noexcept { lastGC_ = currentTimeProvider_(); } - -private: - gc::GCSchedulerConfig& config_; - CurrentTimeProvider currentTimeProvider_; - // Updated by the GC thread, read by the mutators or the timer thread. - std::atomic lastGC_; -}; - -class GCEmptySchedulerData : public gc::GCSchedulerData { - void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {} - void OnPerformFullGC() noexcept override {} - void UpdateAliveSetBytes(size_t bytes) noexcept override {} -}; - -#ifndef KONAN_NO_THREADS - -class GCSchedulerDataWithTimer : public gc::GCSchedulerData { -public: - GCSchedulerDataWithTimer( - gc::GCSchedulerConfig& config, - std::function scheduleGC, - RegularIntervalPacer::CurrentTimeProvider currentTimeProvider) noexcept : - config_(config), - heapGrowthController_(config), - regularIntervalPacer_(config, currentTimeProvider), - scheduleGC_(std::move(scheduleGC)), - timer_("GC Timer thread", config_.regularGcInterval(), [this] { - if (regularIntervalPacer_.NeedsGC()) { - scheduleGC_(); - } - }) {} - - void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { - heapGrowthController_.OnAllocated(threadData.allocatedBytes()); - if (heapGrowthController_.NeedsGC()) { - scheduleGC_(); - } - } - - void OnPerformFullGC() noexcept override { - heapGrowthController_.OnPerformFullGC(); - regularIntervalPacer_.OnPerformFullGC(); - timer_.restart(config_.regularGcInterval()); - } - - void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); } - -private: - gc::GCSchedulerConfig& config_; - HeapGrowthController heapGrowthController_; - RegularIntervalPacer regularIntervalPacer_; - std::function scheduleGC_; - RepeatedTimer<> timer_; -}; - -#endif // !KONAN_NO_THREADS - -class GCSchedulerDataOnSafepoints : public gc::GCSchedulerData { -public: - GCSchedulerDataOnSafepoints( - gc::GCSchedulerConfig& config, - std::function scheduleGC, - RegularIntervalPacer::CurrentTimeProvider currentTimeProvider) noexcept : - heapGrowthController_(config), regularIntervalPacer_(config, currentTimeProvider), scheduleGC_(std::move(scheduleGC)) {} - - void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { - heapGrowthController_.OnAllocated(threadData.allocatedBytes()); - if (heapGrowthController_.NeedsGC()) { - scheduleGC_(); - } else if (regularIntervalPacer_.NeedsGC()) { - scheduleGC_(); - } - } - - void OnPerformFullGC() noexcept override { - heapGrowthController_.OnPerformFullGC(); - regularIntervalPacer_.OnPerformFullGC(); - } - - void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); } - -private: - HeapGrowthController heapGrowthController_; - RegularIntervalPacer regularIntervalPacer_; - std::function scheduleGC_; -}; - -class GCSchedulerDataAggressive : public gc::GCSchedulerData { -public: - GCSchedulerDataAggressive(gc::GCSchedulerConfig& config, std::function scheduleGC) noexcept : - scheduleGC_(std::move(scheduleGC)) { - // TODO: Make it even more aggressive and run on a subset of backend.native tests. - config.threshold = 1000; - config.allocationThresholdBytes = 10000; - } - - void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { scheduleGC_(); } - - void OnPerformFullGC() noexcept override {} - void UpdateAliveSetBytes(size_t bytes) noexcept override {} - -private: - std::function scheduleGC_; -}; - -} // namespace - -KStdUniquePtr kotlin::gc::internal::MakeGCSchedulerData( - SchedulerType type, - gc::GCSchedulerConfig& config, - std::function scheduleGC, - std::function()> currentTimeProvider) noexcept { +KStdUniquePtr MakeGCSchedulerData( + gc::SchedulerType type, gc::GCSchedulerConfig& config, std::function scheduleGC) noexcept { switch (type) { - case SchedulerType::kDisabled: + case gc::SchedulerType::kDisabled: RuntimeLogDebug({kTagGC}, "GC scheduler disabled"); - return ::make_unique(); - case SchedulerType::kWithTimer: + return ::make_unique(); + case gc::SchedulerType::kWithTimer: #ifndef KONAN_NO_THREADS RuntimeLogDebug({kTagGC}, "Initializing timer-based GC scheduler"); - return ::make_unique(config, std::move(scheduleGC), std::move(currentTimeProvider)); + return ::make_unique>(config, std::move(scheduleGC)); #else RuntimeFail("GC scheduler with timer is not supported on this platform"); #endif - case SchedulerType::kOnSafepoints: + case gc::SchedulerType::kOnSafepoints: RuntimeLogDebug({kTagGC}, "Initializing safe-point-based GC scheduler"); - return ::make_unique(config, std::move(scheduleGC), std::move(currentTimeProvider)); - case SchedulerType::kAggressive: + return ::make_unique>(config, std::move(scheduleGC)); + case gc::SchedulerType::kAggressive: RuntimeLogDebug({kTagGC}, "Initializing aggressive GC scheduler"); - return ::make_unique(config, std::move(scheduleGC)); + return ::make_unique(config, std::move(scheduleGC)); } } +} // namespace + void gc::GCScheduler::SetScheduleGC(std::function scheduleGC) noexcept { RuntimeAssert(static_cast(scheduleGC), "scheduleGC cannot be empty"); RuntimeAssert(!static_cast(scheduleGC_), "scheduleGC must not have been set"); scheduleGC_ = std::move(scheduleGC); RuntimeAssert(gcData_ == nullptr, "gcData_ must not be set prior to scheduleGC call"); - gcData_ = internal::MakeGCSchedulerData( - compiler::getGCSchedulerType(), config_, scheduleGC_, []() { return std::chrono::steady_clock::now(); }); + gcData_ = MakeGCSchedulerData(compiler::getGCSchedulerType(), config_, scheduleGC_); } diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.hpp b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.hpp index eb82a7fbb6d..2a237853fbc 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.hpp +++ b/kotlin-native/runtime/src/gc/common/cpp/GCScheduler.hpp @@ -163,16 +163,6 @@ private: std::function scheduleGC_; }; -namespace internal { - -KStdUniquePtr MakeGCSchedulerData( - SchedulerType type, - GCSchedulerConfig& config, - std::function scheduleGC, - std::function()> currentTime) noexcept; - -} - } // namespace gc } // namespace kotlin diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerImpl.hpp b/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerImpl.hpp new file mode 100644 index 00000000000..36c66cc0e03 --- /dev/null +++ b/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerImpl.hpp @@ -0,0 +1,175 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#pragma once + +#include "GCScheduler.hpp" + +#include "Clock.hpp" + +#ifndef KONAN_NO_THREADS +#include "RepeatedTimer.hpp" +#endif + +namespace kotlin::gc::internal { + +class HeapGrowthController { +public: + explicit HeapGrowthController(gc::GCSchedulerConfig& config) noexcept : config_(config) {} + + // Called by the mutators. + void OnAllocated(size_t allocatedBytes) noexcept { allocatedBytes_ += allocatedBytes; } + + // Called by the GC thread. + void OnPerformFullGC() noexcept { allocatedBytes_ = 0; } + + // Called by the GC thread. + void UpdateAliveSetBytes(size_t bytes) noexcept { + lastAliveSetBytes_ = bytes; + + if (config_.autoTune.load()) { + double targetHeapBytes = static_cast(bytes) / config_.targetHeapUtilization; + if (!std::isfinite(targetHeapBytes)) { + // This shouldn't happen in practice: targetHeapUtilization is in (0, 1]. But in case it does, don't touch anything. + return; + } + double minHeapBytes = static_cast(config_.minHeapBytes.load()); + double maxHeapBytes = static_cast(config_.maxHeapBytes.load()); + targetHeapBytes = std::min(std::max(targetHeapBytes, minHeapBytes), maxHeapBytes); + config_.targetHeapBytes = static_cast(targetHeapBytes); + } + } + + // Called by the mutators. + bool NeedsGC() const noexcept { + uint64_t currentHeapBytes = allocatedBytes_.load() + lastAliveSetBytes_.load(); + uint64_t targetHeapBytes = config_.targetHeapBytes; + return currentHeapBytes >= targetHeapBytes; + } + +private: + gc::GCSchedulerConfig& config_; + // Updated by both the mutators and the GC thread. + std::atomic allocatedBytes_ = 0; + // Updated by the GC thread, read by the mutators. + std::atomic lastAliveSetBytes_ = 0; +}; + +template +class RegularIntervalPacer { +public: + using TimePoint = std::chrono::time_point; + + explicit RegularIntervalPacer(gc::GCSchedulerConfig& config) noexcept : config_(config), lastGC_(Clock::now()) {} + + // Called by the mutators or the timer thread. + bool NeedsGC() const noexcept { + auto currentTime = Clock::now(); + return currentTime >= lastGC_.load() + config_.regularGcInterval(); + } + + // Called by the GC thread. + void OnPerformFullGC() noexcept { lastGC_ = Clock::now(); } + +private: + gc::GCSchedulerConfig& config_; + // Updated by the GC thread, read by the mutators or the timer thread. + std::atomic lastGC_; +}; + +class GCEmptySchedulerData : public gc::GCSchedulerData { + void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override {} + void OnPerformFullGC() noexcept override {} + void UpdateAliveSetBytes(size_t bytes) noexcept override {} +}; + +#ifndef KONAN_NO_THREADS + +template +class GCSchedulerDataWithTimer : public gc::GCSchedulerData { +public: + GCSchedulerDataWithTimer(gc::GCSchedulerConfig& config, std::function scheduleGC) noexcept : + config_(config), + heapGrowthController_(config), + regularIntervalPacer_(config), + scheduleGC_(std::move(scheduleGC)), + timer_("GC Timer thread", config_.regularGcInterval(), [this] { + if (regularIntervalPacer_.NeedsGC()) { + scheduleGC_(); + } + }) {} + + void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { + heapGrowthController_.OnAllocated(threadData.allocatedBytes()); + if (heapGrowthController_.NeedsGC()) { + scheduleGC_(); + } + } + + void OnPerformFullGC() noexcept override { + heapGrowthController_.OnPerformFullGC(); + regularIntervalPacer_.OnPerformFullGC(); + timer_.restart(config_.regularGcInterval()); + } + + void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); } + +private: + gc::GCSchedulerConfig& config_; + HeapGrowthController heapGrowthController_; + RegularIntervalPacer regularIntervalPacer_; + std::function scheduleGC_; + RepeatedTimer timer_; +}; + +#endif // !KONAN_NO_THREADS + +template +class GCSchedulerDataOnSafepoints : public gc::GCSchedulerData { +public: + GCSchedulerDataOnSafepoints(gc::GCSchedulerConfig& config, std::function scheduleGC) noexcept : + heapGrowthController_(config), regularIntervalPacer_(config), scheduleGC_(std::move(scheduleGC)) {} + + void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { + heapGrowthController_.OnAllocated(threadData.allocatedBytes()); + if (heapGrowthController_.NeedsGC()) { + scheduleGC_(); + } else if (regularIntervalPacer_.NeedsGC()) { + scheduleGC_(); + } + } + + void OnPerformFullGC() noexcept override { + heapGrowthController_.OnPerformFullGC(); + regularIntervalPacer_.OnPerformFullGC(); + } + + void UpdateAliveSetBytes(size_t bytes) noexcept override { heapGrowthController_.UpdateAliveSetBytes(bytes); } + +private: + HeapGrowthController heapGrowthController_; + RegularIntervalPacer regularIntervalPacer_; + std::function scheduleGC_; +}; + +class GCSchedulerDataAggressive : public gc::GCSchedulerData { +public: + GCSchedulerDataAggressive(gc::GCSchedulerConfig& config, std::function scheduleGC) noexcept : + scheduleGC_(std::move(scheduleGC)) { + // TODO: Make it even more aggressive and run on a subset of backend.native tests. + config.threshold = 1000; + config.allocationThresholdBytes = 10000; + } + + void UpdateFromThreadData(gc::GCSchedulerThreadData& threadData) noexcept override { scheduleGC_(); } + + void OnPerformFullGC() noexcept override {} + void UpdateAliveSetBytes(size_t bytes) noexcept override {} + +private: + std::function scheduleGC_; +}; + +} // namespace kotlin::gc::internal diff --git a/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerTest.cpp b/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerTest.cpp index 32abf876fd3..0af3ac74910 100644 --- a/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerTest.cpp +++ b/kotlin-native/runtime/src/gc/common/cpp/GCSchedulerTest.cpp @@ -11,6 +11,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "ClockTestSupport.hpp" +#include "GCSchedulerImpl.hpp" #include "SingleThreadExecutor.hpp" #include "TestSupport.hpp" @@ -281,8 +283,6 @@ TEST(GCSchedulerThreadDataTest, UpdateThresholdsAfterAllocationSafePoint) { EXPECT_THAT(scheduler.safePointsCounter(), 0); } -using TimePoint = std::chrono::time_point; - class MutatorThread : private Pinned { public: MutatorThread(GCSchedulerConfig& config, std::function slowPath) : @@ -309,60 +309,62 @@ private: SingleThreadExecutor executor_; }; -template +template class GCSchedulerDataTestApi { public: - static constexpr TimePoint initialTime = TimePoint(); - - explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) { - ON_CALL(currentTime_, Call()).WillByDefault([&]() { return time_.load(); }); - - scheduler_ = internal::MakeGCSchedulerData(schedulerType, config, scheduleGC_.AsStdFunction(), currentTime_.AsStdFunction()); - + explicit GCSchedulerDataTestApi(GCSchedulerConfig& config) : scheduler_(config, scheduleGC_.AsStdFunction()) { mutators_.reserve(MutatorCount); for (int i = 0; i < MutatorCount; ++i) { mutators_.emplace_back(make_unique( - config, [this](GCSchedulerThreadData& threadData) { scheduler_->UpdateFromThreadData(threadData); })); + config, [this](GCSchedulerThreadData& threadData) { scheduler_.UpdateFromThreadData(threadData); })); } } std::future Allocate(int mutator, size_t bytes) { return mutators_[mutator]->Allocate(bytes); } - void OnPerformFullGC() { scheduler_->OnPerformFullGC(); } + void OnPerformFullGC() { scheduler_.OnPerformFullGC(); } - void UpdateAliveSetBytes(size_t bytes) { scheduler_->UpdateAliveSetBytes(bytes); } + void UpdateAliveSetBytes(size_t bytes) { scheduler_.UpdateAliveSetBytes(bytes); } testing::MockFunction& scheduleGC() { return scheduleGC_; } template void advance_time(Duration duration) { - auto time = time_.load(); - while (true) { - auto newTime = time + std::chrono::duration_cast(duration); - if (time_.compare_exchange_weak(time, newTime)) { - // TODO: Figure out mocking out RepeatedTimer (or clock underneath it) to avoid sleeping. - std::this_thread::sleep_for(duration); - return; - } - } + test_support::manual_clock::sleep_for(duration); } private: - std::atomic time_ = initialTime; KStdVector> mutators_; testing::MockFunction scheduleGC_; - testing::NiceMock> currentTime_; - KStdUniquePtr scheduler_; + GCScheduler scheduler_; }; -TEST(GCSchedulerDataOnSafePoints, CollectOnTargetHeapReached) { +template +using GCSchedulerDataOnSafepointsTestApi = + GCSchedulerDataTestApi, MutatorCount>; + +template +using GCSchedulerDataWithTimerTestApi = + GCSchedulerDataTestApi, MutatorCount>; + +class GCSchedulerDataTest : public ::testing::Test { +public: + GCSchedulerDataTest() { test_support::manual_clock::reset(); } +}; + +class GCSchedulerDataOnSafePointsTest : public GCSchedulerDataTest {}; +class GCSchedulerDataWithTimerTest : public GCSchedulerDataTest {}; + +TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTargetHeapReached) { + test_support::manual_clock::reset(); + constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = (mutatorsCount + 1) * 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataOnSafepointsTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); KStdVector> futures; @@ -392,17 +394,17 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTargetHeapReached) { schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) { +TEST_F(GCSchedulerDataOnSafePointsTest, CollectOnTimeoutReached) { constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = std::numeric_limits::max(); - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataOnSafepointsTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(10)); + schedulerTestApi.advance_time(microseconds(5)); KStdVector> futures; for (int i = 0; i < mutatorsCount; ++i) { futures.push_back(schedulerTestApi.Allocate(i, 0)); @@ -413,52 +415,52 @@ TEST(GCSchedulerDataOnSafePoints, CollectOnTimeoutReached) { testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); - schedulerTestApi.advance_time(std::chrono::milliseconds(15)); + schedulerTestApi.advance_time(microseconds(5)); schedulerTestApi.Allocate(0, 0).get(); testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); schedulerTestApi.OnPerformFullGC(); schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataOnSafePoints, FullTimeoutAfterLastGC) { +TEST_F(GCSchedulerDataOnSafePointsTest, FullTimeoutAfterLastGC) { constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(20)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataOnSafepointsTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); - schedulerTestApi.advance_time(std::chrono::milliseconds(10)); + schedulerTestApi.advance_time(microseconds(5)); schedulerTestApi.Allocate(0, 10).get(); testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); schedulerTestApi.OnPerformFullGC(); schedulerTestApi.UpdateAliveSetBytes(0); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(15)); + schedulerTestApi.advance_time(microseconds(5)); schedulerTestApi.Allocate(0, 0).get(); - // It's now 25 ms since the start, but only 15ms since previous collection. + // It's now 10 us since the start, but only 5 us since previous collection. testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); - schedulerTestApi.advance_time(std::chrono::milliseconds(10)); + schedulerTestApi.advance_time(microseconds(5)); schedulerTestApi.Allocate(0, 0).get(); - // It's now 25 ms since the previous collection. + // It's now 10 us since the previous collection. testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); schedulerTestApi.OnPerformFullGC(); schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) { +TEST_F(GCSchedulerDataOnSafePointsTest, DoNotTuneTargetHeap) { constexpr int mutatorsCount = 1; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataOnSafepointsTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); schedulerTestApi.Allocate(0, 10).get(); @@ -469,17 +471,17 @@ TEST(GCSchedulerDataOnSafePoints, DoNotTuneTargetHeap) { EXPECT_THAT(config.targetHeapBytes.load(), 10); } -TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) { +TEST_F(GCSchedulerDataOnSafePointsTest, TuneTargetHeap) { constexpr int mutatorsCount = 1; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = true; config.targetHeapBytes = 10; config.targetHeapUtilization = 0.5; config.minHeapBytes = 5; config.maxHeapBytes = 50; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataOnSafepointsTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); schedulerTestApi.Allocate(0, 10).get(); @@ -538,14 +540,14 @@ TEST(GCSchedulerDataOnSafePoints, TuneTargetHeap) { EXPECT_THAT(config.targetHeapBytes.load(), 5); } -TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) { +TEST_F(GCSchedulerDataWithTimerTest, CollectOnTargetHeapReached) { constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = (mutatorsCount + 1) * 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataWithTimerTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); KStdVector> futures; @@ -575,72 +577,59 @@ TEST(GCSchedulerDataWithTimer, CollectOnTargetHeapReached) { schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataWithTimer, CollectOnTimeoutReached) { +TEST_F(GCSchedulerDataWithTimerTest, CollectOnTimeoutReached) { constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = std::numeric_limits::max(); - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataWithTimerTestApi schedulerTestApi(config); - EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(1000)); - testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); + // Wait until the timer is initialized. + test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10)); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); - schedulerTestApi.advance_time(std::chrono::milliseconds(1500)); + schedulerTestApi.advance_time(microseconds(10)); + test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10)); testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); schedulerTestApi.OnPerformFullGC(); schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataWithTimer, FullTimeoutAfterLastGC) { +TEST_F(GCSchedulerDataWithTimerTest, FullTimeoutAfterLastGC) { constexpr int mutatorsCount = kDefaultThreadCount; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::milliseconds(2000)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataWithTimerTestApi schedulerTestApi(config); - EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(1000)); - testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); + // Wait until the timer is initialized. + test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10)); + schedulerTestApi.advance_time(microseconds(5)); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); schedulerTestApi.Allocate(0, 10).get(); testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); schedulerTestApi.OnPerformFullGC(); schedulerTestApi.UpdateAliveSetBytes(0); + // pending should restart to be 10us since the previous collection without scheduling another GC. EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(1500)); - // It's now 250 ms since the start, but only 150ms since previous collection. - // However, the timer has fired once ~50ms ago. + test_support::manual_clock::waitForPending(test_support::manual_clock::now() + microseconds(10)); testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); - - EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()).Times(0); - schedulerTestApi.advance_time(std::chrono::milliseconds(1000)); - // It's now 250 ms since the previous collection, but the timer will fire in ~50ms. - testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); - - EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); - schedulerTestApi.advance_time(std::chrono::milliseconds(1000)); - // 350ms since previous collection, and the timer has fired ~50ms ago. - testing::Mock::VerifyAndClearExpectations(&schedulerTestApi.scheduleGC()); - schedulerTestApi.OnPerformFullGC(); - schedulerTestApi.UpdateAliveSetBytes(0); } -TEST(GCSchedulerDataWithTimer, DoNotTuneTargetHeap) { +TEST_F(GCSchedulerDataWithTimerTest, DoNotTuneTargetHeap) { constexpr int mutatorsCount = 1; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = false; config.targetHeapBytes = 10; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataWithTimerTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); schedulerTestApi.Allocate(0, 10).get(); @@ -651,17 +640,17 @@ TEST(GCSchedulerDataWithTimer, DoNotTuneTargetHeap) { EXPECT_THAT(config.targetHeapBytes.load(), 10); } -TEST(GCSchedulerDataWithTimer, TuneTargetHeap) { +TEST_F(GCSchedulerDataWithTimerTest, TuneTargetHeap) { constexpr int mutatorsCount = 1; GCSchedulerConfig config; - config.regularGcIntervalMicroseconds = std::chrono::microseconds(std::chrono::minutes(10)).count(); + config.regularGcIntervalMicroseconds = 10; config.autoTune = true; config.targetHeapBytes = 10; config.targetHeapUtilization = 0.5; config.minHeapBytes = 5; config.maxHeapBytes = 50; - GCSchedulerDataTestApi schedulerTestApi(config); + GCSchedulerDataWithTimerTestApi schedulerTestApi(config); EXPECT_CALL(schedulerTestApi.scheduleGC(), Call()); schedulerTestApi.Allocate(0, 10).get();