From 3e8c1be5c1a0291330f226b8f6a4ac1b3463c692 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Thu, 16 Sep 2021 18:42:31 +0300 Subject: [PATCH] [K/N] Add RepeatedTimer --- .../runtime/src/main/cpp/RepeatedTimer.hpp | 58 +++++++++++++++++++ .../src/main/cpp/RepeatedTimerTest.cpp | 58 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp create mode 100644 kotlin-native/runtime/src/main/cpp/RepeatedTimerTest.cpp diff --git a/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp b/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp new file mode 100644 index 00000000000..e017c1f47f3 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/RepeatedTimer.hpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2021 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. + */ + +#ifndef RUNTIME_REPEATED_TIMER_H +#define RUNTIME_REPEATED_TIMER_H + +#include +#include +#include +#include + +#include "KAssert.h" +#include "Utils.hpp" + +namespace kotlin { + +class RepeatedTimer : private Pinned { +public: + template + RepeatedTimer(std::chrono::duration interval, F f) noexcept : + thread_([this, interval, f]() noexcept { Run(interval, f); }) {} + + ~RepeatedTimer() { + { + std::unique_lock lock(mutex_); + run_ = false; + } + wait_.notify_all(); + thread_.join(); + } + +private: + template + void Run(std::chrono::duration interval, F f) noexcept { + while (true) { + std::unique_lock lock(mutex_); + if (wait_.wait_for(lock, interval, [this]() noexcept { return !run_; })) { + RuntimeAssert(!run_, "Can only happen once run_ is set to false"); + return; + } + RuntimeAssert(run_, "Can only happen if we timed out on waiting and run_ is still true"); + auto newInterval = f(); + // The next waiting will use the new interval. + interval = std::chrono::duration_cast>(newInterval); + } + } + + std::mutex mutex_; + std::condition_variable wait_; + bool run_ = true; + std::thread thread_; +}; + +} // namespace kotlin + +#endif // RUNTIME_REPEATED_TIMER_H diff --git a/kotlin-native/runtime/src/main/cpp/RepeatedTimerTest.cpp b/kotlin-native/runtime/src/main/cpp/RepeatedTimerTest.cpp new file mode 100644 index 00000000000..d78e7588356 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/RepeatedTimerTest.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2021 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. + */ + +#include "RepeatedTimer.hpp" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace kotlin; + +TEST(RepeatedTimerTest, WillRun) { + std::atomic counter = 0; + RepeatedTimer timer(std::chrono::milliseconds(10), [&counter]() { + ++counter; + return std::chrono::milliseconds(10); + }); + // The function is not executed immediately. + EXPECT_THAT(counter.load(), 0); + // And now wait until the counter increases at least twice + while (counter < 2) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +TEST(RepeatedTimerTest, WillStop) { + std::atomic counter = 0; + { + RepeatedTimer timer(std::chrono::minutes(1), [&counter]() { + ++counter; + return std::chrono::minutes(10); + }); + } + // The function was never executed. + EXPECT_THAT(counter.load(), 0); +} + +TEST(RepeatedTimerTest, AdjustInterval) { + std::atomic counter = 0; + RepeatedTimer timer(std::chrono::milliseconds(1), [&counter]() { + ++counter; + if (counter < 2) { + return std::chrono::milliseconds(1); + } else { + return std::chrono::duration_cast(std::chrono::minutes(10)); + } + }); + // Wait until counter grows to 2, when the waiting time changes to 10 minutes. + while (counter < 2) { + } + EXPECT_THAT(counter.load(), 2); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + // After we've slept for 10ms, we still haven't executed the function another time. + EXPECT_THAT(counter.load(), 2); +}