diff --git a/kotlin-native/runtime/src/main/cpp/Clock.hpp b/kotlin-native/runtime/src/main/cpp/Clock.hpp new file mode 100644 index 00000000000..2144d46b75c --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/Clock.hpp @@ -0,0 +1,183 @@ +/* + * 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 +#include +#include +#include + +#include "Saturating.hpp" + +namespace kotlin { + +namespace internal { + +template +Ret waitUntilViaFor( + NowF&& nowF, + StepDuration step, + std::chrono::time_point, Period>> until, + Ret timeoutValue, + WaitForF&& waitForF) { + while (true) { + auto current = std::invoke(std::forward(nowF)); + if (current >= until) { + return timeoutValue; + } + auto left = until - current; + // Shield standard library from saturating types. + auto interval = left > step ? std::chrono::duration(step) : std::chrono::duration(left); + if (auto value = std::invoke(std::forward(waitForF), interval); value != timeoutValue) { + return value; + } + } +} + +template +void waitUntilViaFor( + NowF&& nowF, + StepDuration step, + std::chrono::time_point, Period>> until, + WaitForF&& waitForF) { + while (true) { + auto current = std::invoke(std::forward(nowF)); + if (current >= until) { + return; + } + auto left = until - current; + // Shield standard library from saturating types. + auto interval = left > step ? std::chrono::duration(step) : std::chrono::duration(left); + std::invoke(std::forward(waitForF), interval); + } +} + +template +struct IsStdCV : public std::false_type {}; + +template <> +struct IsStdCV> : public std::true_type {}; + +template +struct IsStdCV : public std::true_type {}; + +template +inline constexpr bool isStdCV = IsStdCV::value; + +template +struct IsStdFuture : public std::false_type {}; + +template +struct IsStdFuture> : public std::true_type {}; + +template +struct IsStdFuture> : public std::true_type {}; + +template +inline constexpr bool isStdFuture = IsStdFuture::value; + +template +class ClockWaitImpl { +public: + template + static void sleep_for(std::chrono::duration interval) { + // Not using this_thread::sleep_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this. + return ClockWaitImpl::sleep_until(Clock::now() + interval); + } + + template + static void sleep_until(std::chrono::time_point> until) { + if constexpr (is_saturating_v) { + return Clock::sleepImpl(until); + } else { + return ClockWaitImpl::sleep_until(std::chrono::time_point, Period>>(until)); + } + } + + template >> + static bool wait_for(CV& cv, Lock& lock, std::chrono::duration interval, F&& f) { + // Not using cv.wait_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this. + return ClockWaitImpl::wait_until(cv, lock, Clock::now() + interval, std::forward(f)); + } + + template >> + static bool wait_until(CV& cv, Lock& lock, std::chrono::time_point> until, F&& f) { + if constexpr (is_saturating_v) { + [[maybe_unused]] auto pendingWait = Clock::addPendingWait(until); + // Implement in terms of repeated cv.wait_for of non-"infinite" intervals. + return internal::waitUntilViaFor(&Clock::now, Clock::wait_step, until, false, [&](auto interval) { + return cv.wait_for(lock, interval, std::forward(f)); + }); + } else { + return ClockWaitImpl::wait_until( + cv, lock, std::chrono::time_point, Period>>(until), std::forward(f)); + } + } + + template >> + static std::future_status wait_for(const Future& future, std::chrono::duration interval) { + // Not using future.wait_for, because it may mishandle "infinite" intervals. Use saturating arithmetics to address this. + return ClockWaitImpl::wait_until(future, Clock::now() + interval); + } + + template >> + static std::future_status wait_until(const Future& future, std::chrono::time_point> until) { + if constexpr (is_saturating_v) { + [[maybe_unused]] auto pendingWait = Clock::addPendingWait(until); + // Implement in terms of repeated future.wait_for of non-"infinite" intervals. + return internal::waitUntilViaFor(&Clock::now, Clock::wait_step, until, std::future_status::timeout, [&](auto interval) { + return future.wait_for(interval); + }); + } else { + return ClockWaitImpl::wait_until( + future, std::chrono::time_point, Period>>(until)); + } + } +}; + +} // namespace internal + +using nanoseconds = std::chrono::duration, std::chrono::nanoseconds::period>; +using microseconds = std::chrono::duration, std::chrono::microseconds::period>; +using milliseconds = std::chrono::duration, std::chrono::milliseconds::period>; +using seconds = std::chrono::duration, std::chrono::seconds::period>; +using minutes = std::chrono::duration, std::chrono::minutes::period>; +using hours = std::chrono::duration, std::chrono::hours::period>; + +class steady_clock : public internal::ClockWaitImpl { +public: + using rep = saturating; + using period = std::chrono::steady_clock::period; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + + static constexpr bool is_steady = true; + + static time_point now() noexcept { + auto time = std::chrono::steady_clock::now().time_since_epoch(); + return time_point(time); + } + +private: + friend class internal::ClockWaitImpl; + + // Use non-saturating type here, because step may be fed into the standard library. + static inline constexpr auto wait_step = std::chrono::hours(24); + + template + static void sleepImpl(std::chrono::time_point, Period>> until) { + // Implement in terms of repeated this_thread::sleep_for of non-"infinite" intervals. + return internal::waitUntilViaFor(&now, wait_step, until, [&](auto interval) { std::this_thread::sleep_for(interval); }); + } + + template + static int addPendingWait(std::chrono::time_point, Period>> until) { + // No need to register here. + return 0; + } +}; + +} // namespace kotlin diff --git a/kotlin-native/runtime/src/main/cpp/ClockTest.cpp b/kotlin-native/runtime/src/main/cpp/ClockTest.cpp new file mode 100644 index 00000000000..2630d2dc2b7 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ClockTest.cpp @@ -0,0 +1,1118 @@ +/* + * 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. + */ + +#include "Clock.hpp" + +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "ClockTestSupport.hpp" +#include "ScopedThread.hpp" +#include "TestSupport.hpp" + +using namespace kotlin; + +TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateOK) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + constexpr int okValue = 13; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, okValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_EventualOK) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + constexpr int okValue = 13; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(okValue)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, okValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_LastChanceOK) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + constexpr int okValue = 13; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); + EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(okValue)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, okValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_Timeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); + EXPECT_CALL(waitForF, Call(rest)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, timeoutValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_ImmediateTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, timeoutValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_ClockJumpTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + // Instead of incrementing by `step`, the clock jumped straight to `until`. + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until)); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, timeoutValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Int_NonconformantWaitTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + // waitFor non-conformingly waits less than specified. + constexpr auto actualStep = std::chrono::seconds(7); + constexpr auto rest = std::chrono::seconds(3); + constexpr auto until = TimePoint() + step + step + rest; + constexpr int timeoutValue = 42; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep)); + EXPECT_CALL(waitForF, Call(step)).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep)); + // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds. + EXPECT_CALL(waitForF, Call(std::chrono::seconds(9))).WillOnce(testing::Return(timeoutValue)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6))); + EXPECT_CALL(waitForF, Call(std::chrono::seconds(3))).WillOnce(testing::Return(timeoutValue)); + // Finally waited enough. + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9))); + } + auto result = internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, timeoutValue, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); + EXPECT_THAT(result, timeoutValue); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Void_Timeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step)); + EXPECT_CALL(waitForF, Call(step)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step)); + EXPECT_CALL(waitForF, Call(rest)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + rest)); + } + internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Void_ImmediateTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + step + step + step)); + } + internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Void_ClockJumpTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + constexpr auto rest = std::chrono::seconds(1); + constexpr auto until = TimePoint() + step + step + rest; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)); + // Instead of incrementing by `step`, the clock jumped straight to `until`. + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(until)); + } + internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); +} + +TEST(ClockInternalTest, WaitUntilViaFor_Void_NonconformantWaitTimeout) { + using TimePoint = std::chrono::time_point; + testing::StrictMock> nowF; + constexpr auto step = std::chrono::seconds(10); + // waitFor non-conformingly waits less than specified. + constexpr auto actualStep = std::chrono::seconds(7); + constexpr auto rest = std::chrono::seconds(3); + constexpr auto until = TimePoint() + step + step + rest; + testing::StrictMock> waitForF; + + { + testing::InSequence s; + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint())); + EXPECT_CALL(waitForF, Call(step)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep)); + EXPECT_CALL(waitForF, Call(step)); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep)); + // Waited 2 * 7 out of 2 * 10 + 3 seconds. 9 seconds left. Will wait only 6 seconds. + EXPECT_CALL(waitForF, Call(std::chrono::seconds(9))); + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(6))); + EXPECT_CALL(waitForF, Call(std::chrono::seconds(3))); + // Finally waited enough. + EXPECT_CALL(nowF, Call()).WillOnce(testing::Return(TimePoint() + actualStep + actualStep + std::chrono::seconds(9))); + } + internal::waitUntilViaFor(nowF.AsStdFunction(), step, until, [&](auto interval) { + return waitForF.Call(std::chrono::duration_cast(interval)); + }); +} + +namespace { + +class ClockTestNames { +public: + template + static std::string GetName(int) { + if constexpr (std::is_same_v) { + return "steady_clock"; + } else if constexpr (std::is_same_v) { + return "manual_clock"; + } else { + return "unknown"; + } + } +}; + +} // namespace + +template +class ClockTest : public testing::Test { +public: + ClockTest() noexcept { test_support::manual_clock::reset(); } +}; + +using ClockTestTypes = testing::Types; +TYPED_TEST_SUITE(ClockTest, ClockTestTypes, ClockTestNames); + +TYPED_TEST(ClockTest, SleepFor) { + constexpr auto interval = milliseconds(1); + auto before = TypeParam::now(); + TypeParam::sleep_for(interval); + auto after = TypeParam::now(); + EXPECT_THAT(after - before, testing::Ge(interval)); +} + +TYPED_TEST(ClockTest, SleepUntil) { + auto until = TypeParam::now() + milliseconds(1); + TypeParam::sleep_until(until); + auto after = TypeParam::now(); + EXPECT_THAT(after, testing::Ge(until)); +} + +TYPED_TEST(ClockTest, CVWaitFor_OK) { + constexpr auto interval = hours(10); + std::condition_variable cv; + std::mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; }); + auto after = TypeParam::now(); + EXPECT_TRUE(result); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, CVWaitFor_Timeout) { + constexpr auto interval = microseconds(10); + std::condition_variable cv; + std::mutex m; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + std::unique_lock guard(m); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; }); + auto after = TypeParam::now(); + EXPECT_FALSE(result); + EXPECT_THAT(after - before, testing::Ge(interval)); +} + +TYPED_TEST(ClockTest, CVWaitFor_InfiniteTimeout) { + std::condition_variable cv; + std::mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_for` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + go = true; + auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; }); + EXPECT_TRUE(result); +} + +TYPED_TEST(ClockTest, CVWaitUntil_OK) { + std::condition_variable cv; + std::mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + auto until = TypeParam::now() + hours(10); + go = true; + auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; }); + auto after = TypeParam::now(); + EXPECT_TRUE(result); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, CVWaitUntil_Timeout) { + constexpr auto interval = microseconds(10); + std::condition_variable cv; + std::mutex m; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + std::unique_lock guard(m); + auto until = TypeParam::now() + interval; + go = true; + auto result = TypeParam::wait_until(cv, guard, until, [] { return false; }); + auto after = TypeParam::now(); + EXPECT_FALSE(result); + EXPECT_THAT(after, testing::Ge(until)); +} + +TYPED_TEST(ClockTest, CVWaitUntil_InfiniteTimeout) { + std::condition_variable cv; + std::mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_until` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + go = true; + auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; }); + EXPECT_TRUE(result); +} + +TYPED_TEST(ClockTest, CVAnyWaitFor_OK) { + constexpr auto interval = hours(10); + std::condition_variable_any cv; + std::shared_mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(cv, guard, interval, [&] { return ok; }); + auto after = TypeParam::now(); + EXPECT_TRUE(result); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, CVAnyWaitFor_Timeout) { + constexpr auto interval = microseconds(10); + std::condition_variable_any cv; + std::shared_mutex m; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + std::unique_lock guard(m); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(cv, guard, interval, [] { return false; }); + auto after = TypeParam::now(); + EXPECT_FALSE(result); + EXPECT_THAT(after - before, testing::Ge(interval)); +} + +TYPED_TEST(ClockTest, CVAnyWaitFor_InfiniteTimeout) { + std::condition_variable_any cv; + std::shared_mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_for` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + go = true; + auto result = TypeParam::wait_for(cv, guard, microseconds::max(), [&] { return ok; }); + EXPECT_TRUE(result); +} + +TYPED_TEST(ClockTest, CVAnyWaitUntil_OK) { + std::condition_variable_any cv; + std::shared_mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + auto until = TypeParam::now() + hours(10); + go = true; + auto result = TypeParam::wait_until(cv, guard, until, [&] { return ok; }); + auto after = TypeParam::now(); + EXPECT_TRUE(result); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, CVAnyWaitUntil_Timeout) { + constexpr auto interval = microseconds(10); + std::condition_variable_any cv; + std::shared_mutex m; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + std::unique_lock guard(m); + auto until = TypeParam::now() + interval; + go = true; + auto result = TypeParam::wait_until(cv, guard, until, [] { return false; }); + auto after = TypeParam::now(); + EXPECT_FALSE(result); + EXPECT_THAT(after, testing::Ge(until)); +} + +TYPED_TEST(ClockTest, CVAnyWaitUntil_InfiniteTimeout) { + std::condition_variable_any cv; + std::shared_mutex m; + bool ok = false; + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_until` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + { + std::unique_lock guard(m); + ok = true; + } + cv.notify_all(); + }); + std::unique_lock guard(m); + go = true; + auto result = TypeParam::wait_until(cv, guard, TypeParam::time_point::max(), [&] { return ok; }); + EXPECT_TRUE(result); +} + +TYPED_TEST(ClockTest, FutureWaitFor_OK) { + constexpr auto interval = hours(10); + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + promise.set_value(42); + }); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::ready); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, FutureWaitFor_Deferred) { + constexpr auto interval = hours(10); + std::future future = std::async(std::launch::deferred, [] { return 42; }); + auto before = TypeParam::now(); + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::deferred); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, FutureWaitFor_Timeout) { + constexpr auto interval = microseconds(10); + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::timeout); + EXPECT_THAT(after - before, testing::Ge(interval)); +} + +TYPED_TEST(ClockTest, FutureWaitFor_InfiniteTimeout) { + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_for` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + promise.set_value(42); + }); + go = true; + auto result = TypeParam::wait_for(future, microseconds::max()); + EXPECT_THAT(result, std::future_status::ready); +} + +TYPED_TEST(ClockTest, FutureWaitUntil_OK) { + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + promise.set_value(42); + }); + auto until = TypeParam::now() + hours(10); + go = true; + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::ready); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, FutureWaitUntil_Deferred) { + std::future future = std::async(std::launch::deferred, [] { return 42; }); + auto until = TypeParam::now() + hours(10); + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::deferred); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, FutureWaitUntil_Timeout) { + constexpr auto interval = microseconds(10); + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + auto until = TypeParam::now() + interval; + go = true; + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::timeout); + EXPECT_THAT(after, testing::Ge(until)); +} + +TYPED_TEST(ClockTest, FutureWaitUntil_InfiniteTimeout) { + std::promise promise; + std::future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_until` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + promise.set_value(42); + }); + go = true; + auto result = TypeParam::wait_until(future, TypeParam::time_point::max()); + EXPECT_THAT(result, std::future_status::ready); +} + +TYPED_TEST(ClockTest, SharedFutureWaitFor_OK) { + constexpr auto interval = hours(10); + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + promise.set_value(42); + }); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::ready); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitFor_Deferred) { + constexpr auto interval = hours(10); + std::shared_future future = std::async(std::launch::deferred, [] { return 42; }); + auto before = TypeParam::now(); + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::deferred); + EXPECT_THAT(after - before, testing::Lt(interval)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitFor_Timeout) { + constexpr auto interval = microseconds(10); + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + auto before = TypeParam::now(); + go = true; + auto result = TypeParam::wait_for(future, interval); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::timeout); + EXPECT_THAT(after - before, testing::Ge(interval)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitFor_InfiniteTimeout) { + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_for` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + promise.set_value(42); + }); + go = true; + auto result = TypeParam::wait_for(future, microseconds::max()); + EXPECT_THAT(result, std::future_status::ready); +} + +TYPED_TEST(ClockTest, SharedFutureWaitUntil_OK) { + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + promise.set_value(42); + }); + auto until = TypeParam::now() + hours(10); + go = true; + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::ready); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitUntil_Deferred) { + std::shared_future future = std::async(std::launch::deferred, [] { return 42; }); + auto until = TypeParam::now() + hours(10); + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::deferred); + EXPECT_THAT(after, testing::Lt(until)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitUntil_Timeout) { + constexpr auto interval = microseconds(10); + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + TypeParam::sleep_for(interval); + }); + auto until = TypeParam::now() + interval; + go = true; + auto result = TypeParam::wait_until(future, until); + auto after = TypeParam::now(); + EXPECT_THAT(result, std::future_status::timeout); + EXPECT_THAT(after, testing::Ge(until)); +} + +TYPED_TEST(ClockTest, SharedFutureWaitUntil_InfiniteTimeout) { + std::promise promise; + std::shared_future future = promise.get_future(); + std::atomic go = false; + ScopedThread thread([&] { + while (!go.load()) { + } + // Wait to see if `TypeParam::wait_until` wakes up from timeout. + TypeParam::sleep_for(milliseconds(1)); + promise.set_value(42); + }); + go = true; + auto result = TypeParam::wait_until(future, TypeParam::time_point::max()); + EXPECT_THAT(result, std::future_status::ready); +} + +namespace { + +class ClockTypesTestNames { +public: + template + static std::string GetName(int) { + std::string result; + result += clockName(); + result += "_"; + result += durationName(); + return result; + } + +private: + template + static const char* clockName() { + using Clock = typename std::tuple_element<0, T>::type; + if constexpr (std::is_same_v) { + return "steady_clock"; + } else if constexpr (std::is_same_v) { + return "manual_clock"; + } else { + return "unknown"; + } + } + + template + static const char* durationName() { + using Duration = typename std::tuple_element<1, T>::type; + if constexpr (std::is_same_v) { + return "ns"; + } else if constexpr (std::is_same_v) { + return "us"; + } else if constexpr (std::is_same_v) { + return "ms"; + } else if constexpr (std::is_same_v) { + return "s"; + } else if constexpr (std::is_same_v) { + return "m"; + } else if constexpr (std::is_same_v) { + return "h"; + } else if constexpr (std::is_same_v) { + return "sat_ns"; + } else if constexpr (std::is_same_v) { + return "sat_us"; + } else if constexpr (std::is_same_v) { + return "sat_ms"; + } else if constexpr (std::is_same_v) { + return "sat_s"; + } else if constexpr (std::is_same_v) { + return "sat_m"; + } else if constexpr (std::is_same_v) { + return "sat_h"; + } else { + return "unknown"; + } + } +}; + +} // namespace + +template +class ClockTypesTest : public testing::Test { +public: + using Clock = typename std::tuple_element<0, T>::type; + using Duration = typename std::tuple_element<1, T>::type; +}; + +using ClockTypesTestTypes = testing::Types< + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple, + std::tuple>; +TYPED_TEST_SUITE(ClockTypesTest, ClockTypesTestTypes, ClockTypesTestNames); + +TYPED_TEST(ClockTypesTest, SleepFor) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v()))>); +} + +TYPED_TEST(ClockTypesTest, SleepUntil) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v>()))>); +} + +TYPED_TEST(ClockTypesTest, CVWaitFor) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + bool, + decltype(Clock::wait_for( + std::declval(), std::declval&>(), std::declval(), + std::declval>()))>); +} + +TYPED_TEST(ClockTypesTest, CVWaitUntil) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + bool, + decltype(Clock::wait_until( + std::declval(), std::declval&>(), + std::declval>(), std::declval>()))>); +} + +TYPED_TEST(ClockTypesTest, CVAnyWaitFor) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + bool, + decltype(Clock::wait_for( + std::declval(), std::declval&>(), + std::declval(), std::declval>()))>); +} + +TYPED_TEST(ClockTypesTest, CVAnyWaitUntil) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + bool, + decltype(Clock::wait_until( + std::declval(), std::declval&>(), + std::declval>(), std::declval>()))>); +} + +TYPED_TEST(ClockTypesTest, FutureWaitFor) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + std::future_status, decltype(Clock::wait_for(std::declval&>(), std::declval()))>); +} + +TYPED_TEST(ClockTypesTest, FutureWaitUntil) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + std::future_status, + decltype(Clock::wait_until( + std::declval&>(), std::declval>()))>); +} + +TYPED_TEST(ClockTypesTest, SharedFutureWaitFor) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + std::future_status, decltype(Clock::wait_for(std::declval&>(), std::declval()))>); +} + +TYPED_TEST(ClockTypesTest, SharedFutureWaitUntil) { + using Clock = typename ClockTypesTest::Clock; + using Duration = typename ClockTypesTest::Duration; + static_assert(std::is_same_v< + std::future_status, + decltype(Clock::wait_until( + std::declval&>(), std::declval>()))>); +} + +TEST(ManualClockTest, SleepUntil) { + test_support::manual_clock::reset(); + + auto before = test_support::manual_clock::now(); + test_support::manual_clock::sleep_until(before + seconds(2)); + EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); + // Sleep until current time. + test_support::manual_clock::sleep_until(before + seconds(2)); + EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); + // Sleep until moment in the past. + test_support::manual_clock::sleep_until(before); + EXPECT_THAT(test_support::manual_clock::now() - before, seconds(2)); +} + +TEST(ManualClockTest, Pending) { + test_support::manual_clock::reset(); + + // Nothing pending at start. + EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); + std::promise promise; + std::future future = promise.get_future(); + ScopedThread thread([&] { test_support::manual_clock::wait_for(future, seconds(1)); }); + test_support::manual_clock::waitForPending(test_support::manual_clock::now() + seconds(1)); + + // Unblocks the thread. + test_support::manual_clock::sleep_for(seconds(1)); + thread.join(); + + // Nothing pending anymore. + EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); +} + +TEST(ManualClockTest, ConcurrentSleepUntil) { + test_support::manual_clock::reset(); + + constexpr auto threadCount = kDefaultThreadCount; + KStdVector threads; + std::atomic run = false; + std::atomic ready = 0; + for (int i = 0; i < threadCount; ++i) { + threads.emplace_back([&, i] { + auto now = test_support::manual_clock::now(); + ++ready; + while (!run.load()) { + } + test_support::manual_clock::sleep_until(now + seconds(i)); + }); + } + auto before = test_support::manual_clock::now(); + while (ready.load() < threadCount) { + } + run = true; + threads.clear(); + auto after = test_support::manual_clock::now(); + EXPECT_THAT(after - before, seconds(threadCount - 1)); +} + +TEST(ManualClockTest, ConcurrentWaits) { + test_support::manual_clock::reset(); + + constexpr auto threadCount = kDefaultThreadCount; + KStdVector threads; + std::mutex mutex; + std::condition_variable cv; + std::condition_variable_any cvAny; + std::promise promise1; + std::promise promise2; + std::future future = promise1.get_future(); + std::shared_future shared_future = promise2.get_future().share(); + std::atomic run = false; + std::atomic ready = 0; + for (int i = 0; i < threadCount; ++i) { + threads.emplace_back([&, i] { + auto now = test_support::manual_clock::now(); + ++ready; + while (!run.load()) { + } + switch (i % 4) { + case 0: { + std::unique_lock guard(mutex); + test_support::manual_clock::wait_until(cv, guard, now + seconds(i / 3 + 1), [] { return false; }); + } + case 1: { + std::unique_lock guard(mutex); + test_support::manual_clock::wait_until(cvAny, guard, now + seconds(i / 3 + 1), [] { return false; }); + } + case 2: { + test_support::manual_clock::wait_until(future, now + seconds(i / 3 + 1)); + } + case 3: { + test_support::manual_clock::wait_until(shared_future, now + seconds(i / 3 + 1)); + } + } + }); + } + auto before = test_support::manual_clock::now(); + while (ready.load() < threadCount) { + } + run = true; + + test_support::manual_clock::sleep_until(before + seconds(1)); + // Now the first 3 threads will be unblocked. + threads[0].join(); + threads[1].join(); + threads[2].join(); + // Make sure at least one other thread is waiting. + while (!test_support::manual_clock::pending()) { + } + auto pendingAfterSecond = *test_support::manual_clock::pending(); + EXPECT_THAT(pendingAfterSecond, testing::Ge(before + seconds(2))); + + // Unblock all the threads. + test_support::manual_clock::sleep_until(before + seconds((threadCount - 1) / 3 + 1)); + threads.clear(); + // All threads are gone, nothing can possibly be pending. + EXPECT_THAT(test_support::manual_clock::pending(), std::nullopt); +} diff --git a/kotlin-native/runtime/src/main/cpp/ClockTestSupport.cpp b/kotlin-native/runtime/src/main/cpp/ClockTestSupport.cpp new file mode 100644 index 00000000000..21be99d25cb --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ClockTestSupport.cpp @@ -0,0 +1,17 @@ +/* + * 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. + */ + +#include "ClockTestSupport.hpp" + +using namespace kotlin; + +// static +std::atomic test_support::manual_clock::now_ = test_support::manual_clock::time_point::min(); + +// static +std::mutex test_support::manual_clock::pendingWaitsMutex_; + +// static +KStdOrderedMultiset test_support::manual_clock::pendingWaits_; diff --git a/kotlin-native/runtime/src/main/cpp/ClockTestSupport.hpp b/kotlin-native/runtime/src/main/cpp/ClockTestSupport.hpp new file mode 100644 index 00000000000..6d1ed88de45 --- /dev/null +++ b/kotlin-native/runtime/src/main/cpp/ClockTestSupport.hpp @@ -0,0 +1,109 @@ +/* + * 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 "Clock.hpp" + +#include + +#include "gtest/gtest.h" + +#include "Types.h" +#include "Utils.hpp" + +namespace kotlin::test_support { + +// Clock that is manually advanced (cannot go backwards). For testing purposes. +// TODO: Should be reset at the start of each test. +class manual_clock : public internal::ClockWaitImpl { +public: + using duration = nanoseconds; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + + // Steady because it cannot go backwards. + static constexpr bool is_steady = true; + + static time_point now() noexcept { return now_.load(); } + + static std::optional pending() noexcept { + std::unique_lock guard(pendingWaitsMutex_); + auto it = pendingWaits_.begin(); + if (it == pendingWaits_.end()) { + return std::nullopt; + } + return *it; + } + + template + static void waitForPending( + std::chrono::time_point expectedPending, std::chrono::duration timeout) { + // Use real clock for timeout. + auto until = std::chrono::steady_clock::now() + timeout; + while (!pending() || *pending() != expectedPending) { + if (std::chrono::steady_clock::now() > until) { + EXPECT_THAT(pending(), std::make_optional(expectedPending)); + return; + } + } + } + + template + static void waitForPending(std::chrono::time_point expectedPending) { + waitForPending(expectedPending, std::chrono::minutes(5)); + } + + // Should be done before the timer is used (in the beginning of the test, for example). + static void reset(time_point start = time_point::min()) noexcept { + std::unique_lock guard(pendingWaitsMutex_); + RuntimeAssert(pendingWaits_.empty(), "To reset, there must not be any pending waits"); + now_ = start; + } + +private: + friend class internal::ClockWaitImpl; + + // Use non-saturating type here, because step may be fed into the standard library. + static inline constexpr auto wait_step = std::chrono::microseconds(1); + + template + static void sleepImpl(std::chrono::time_point, Period>> until) { + time_point before = now(); + while (before < until) { + now_.compare_exchange_weak(before, until); + } + } + + class PendingWaitRegistration : private Pinned { + public: + ~PendingWaitRegistration() noexcept { + std::unique_lock guard(pendingWaitsMutex_); + pendingWaits_.erase(it_); + } + + private: + friend class manual_clock; + + explicit PendingWaitRegistration(KStdOrderedMultiset::iterator it) noexcept : it_(it) {} + + KStdOrderedMultiset::iterator it_; + }; + + template + static PendingWaitRegistration addPendingWait( + std::chrono::time_point, Period>> until) { + std::unique_lock guard(pendingWaitsMutex_); + auto it = pendingWaits_.insert(until); + return PendingWaitRegistration(it); + } + + static std::atomic now_; + static std::mutex pendingWaitsMutex_; + static KStdOrderedMultiset pendingWaits_; +}; + +} // namespace kotlin::test_support