From a0fc0f15a26476164bfce553c988a3148187f7e6 Mon Sep 17 00:00:00 2001 From: Niklas Hauser Date: Sat, 13 Apr 2024 14:56:14 +0200 Subject: [PATCH] [fiber] Implement sleep_for and sleep_until with polling --- src/modm/processing/fiber/functions.hpp | 87 ++++++++++--- test/modm/processing/fiber/fiber_test.cpp | 146 ++++++++++++++++++++-- test/modm/processing/fiber/fiber_test.hpp | 16 ++- 3 files changed, 217 insertions(+), 32 deletions(-) diff --git a/src/modm/processing/fiber/functions.hpp b/src/modm/processing/fiber/functions.hpp index eeea4ddd54..4d4db51320 100644 --- a/src/modm/processing/fiber/functions.hpp +++ b/src/modm/processing/fiber/functions.hpp @@ -15,6 +15,7 @@ #include "scheduler.hpp" #include +#include namespace modm::this_fiber { @@ -50,29 +51,85 @@ get_id() } /** - * Yields the current fiber until the time interval has elapsed. - * This functionality is a convenience wrapper around `modm::Timeout` - * if interval ≥1ms or `modm::PreciseTimeout` if interval ≥1µs. - * For nanosecond delays, use `modm::delay(ns)`. + * Yields the current fiber until `condition()` returns true or the time + * duration has elapsed. + * + * @returns `true` if the condition was met, `false` if the time duration has + * elapsed. * * @note Due to the overhead of `yield()` and the scheduling other fibers, the - * sleep interval may be longer without any guarantee of an upper limit. + * sleep duration may be longer without any guarantee of an upper limit. */ -template< typename Rep, typename Period > -void -sleep_for(std::chrono::duration interval) +template< class Rep, class Period, class Function > +[[nodiscard]] bool +poll_for(std::chrono::duration sleep_duration, Function &&condition) { // Only choose the microsecond clock if necessary - using TimeoutType = std::conditional_t< + using Clock = std::conditional_t< std::is_convertible_v, std::chrono::duration>, - modm::GenericTimeout< modm::chrono::milli_clock, modm::chrono::milli_clock::duration>, - modm::GenericTimeout< modm::chrono::micro_clock, modm::chrono::micro_clock::duration> - >; + modm::chrono::milli_clock, modm::chrono::micro_clock>; + + const auto start = Clock::now(); + do { + if (condition()) return true; + modm::this_fiber::yield(); + } + while((Clock::now() - start) <= sleep_duration); + return false; +} + +/** + * Yields the current fiber until `condition()` returns true or the sleep time + * has been reached. + * + * @returns `true` if the condition was met, `false` if the sleep time has + * elapsed. + * + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + */ +template< class Clock, class Duration, class Function > +[[nodiscard]] bool +poll_until(std::chrono::time_point sleep_time, Function &&condition) +{ + const auto start = Clock::now(); + const auto sleep_duration = sleep_time - start; + do { + if (condition()) return true; + modm::this_fiber::yield(); + } + while((Clock::now() - start) <= sleep_duration); + return false; +} - TimeoutType timeout(interval); - while(not timeout.isExpired()) - modm::fiber::yield(); +/** + * Yields the current fiber until the time duration has elapsed. + * + * @note For nanosecond delays, use `modm::delay(ns)`. + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + * @see https://en.cppreference.com/w/cpp/thread/sleep_for + */ +template< class Rep, class Period > +void +sleep_for(std::chrono::duration sleep_duration) +{ + (void) poll_for(sleep_duration, [](){ return false; }); +} + +/** + * Yields the current fiber until the sleep time has been reached. + * + * @note Due to the overhead of `yield()` and the scheduling other fibers, the + * sleep duration may be longer without any guarantee of an upper limit. + * @see https://en.cppreference.com/w/cpp/thread/sleep_until + */ +template< class Clock, class Duration > +void +sleep_until(std::chrono::time_point sleep_time) +{ + (void) poll_until(sleep_time, [](){ return false; }); } /// @} diff --git a/test/modm/processing/fiber/fiber_test.cpp b/test/modm/processing/fiber/fiber_test.cpp index 97f110d9fb..89e969ab1a 100644 --- a/test/modm/processing/fiber/fiber_test.cpp +++ b/test/modm/processing/fiber/fiber_test.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Erik Henriksson + * Copyright (c) 2024, Niklas Hauser * * This file is part of the modm project. * @@ -14,9 +15,10 @@ #include #include #include +#include -namespace -{ +using namespace std::chrono_literals; +using test_clock = modm_test::chrono::milli_clock; enum State { @@ -27,6 +29,17 @@ enum State F2_END, F3_START, F3_END, + F4_START, + F4_END, + F5_START, + F5_INCR10, + F5_INCR20, + F5_INCR30, + F5_END, + F6_START, + F6_SLEEP1, + F6_SLEEP2, + F6_END, SUBROUTINE_START, SUBROUTINE_END, CONSUMER_START, @@ -35,12 +48,12 @@ enum State PRODUCER_END, }; -std::array states = {}; -size_t states_pos = 0; - +static std::array states = {}; +static size_t states_pos = 0; +static modm::fiber::Stack<1024> stack1, stack2; #define ADD_STATE(state) states[states_pos++] = state; -void +static void f1() { ADD_STATE(F1_START); @@ -48,7 +61,7 @@ f1() ADD_STATE(F1_END); } -void +static void f2() { ADD_STATE(F2_START); @@ -56,10 +69,6 @@ f2() ADD_STATE(F2_END); } -modm::fiber::Stack<1024> stack1, stack2; - -} // namespace - void FiberTest::testOneFiber() { @@ -84,7 +93,7 @@ FiberTest::testTwoFibers() TEST_ASSERT_EQUALS(states[3], F2_END); } -__attribute__((noinline)) void +static __attribute__((noinline)) void subroutine() { ADD_STATE(SUBROUTINE_START); @@ -92,7 +101,7 @@ subroutine() ADD_STATE(SUBROUTINE_END); } -void +static void f3() { ADD_STATE(F3_START); @@ -114,3 +123,114 @@ FiberTest::testYieldFromSubroutine() TEST_ASSERT_EQUALS(states[4], SUBROUTINE_END); TEST_ASSERT_EQUALS(states[5], F3_END); } + + +void +FiberTest::testPollFor() +{ + test_clock::setTime(1251); + TEST_ASSERT_TRUE(modm::this_fiber::poll_for(20ms, [](){ return true; })); + // timeout is tested in the SleepFor() test +} + + +void +FiberTest::testPollUntil() +{ + test_clock::setTime(451250); + TEST_ASSERT_TRUE(modm::this_fiber::poll_until(modm::Clock::now() + 20ms, [](){ return true; })); + TEST_ASSERT_TRUE(modm::this_fiber::poll_until(modm::Clock::now() - 20ms, [](){ return true; })); + // timeout is tested in the SleepUntil() tests +} + + +static void +f4() +{ + ADD_STATE(F4_START); + modm::this_fiber::sleep_for(50ms); + ADD_STATE(F4_END); +} + +static void +f5() +{ + ADD_STATE(F5_START); + test_clock::increment(10); + ADD_STATE(F5_INCR10); + modm::this_fiber::yield(); + + test_clock::increment(20); + ADD_STATE(F5_INCR20); + modm::this_fiber::yield(); + + test_clock::increment(30); + ADD_STATE(F5_INCR30); + modm::this_fiber::yield(); + + ADD_STATE(F5_END); +} + +static void +runSleepFor(uint32_t startTime) +{ + test_clock::setTime(startTime); + states_pos = 0; + modm::fiber::Task fiber1(stack1, f4), fiber2(stack2, f5); + modm::fiber::Scheduler::run(); + + TEST_ASSERT_EQUALS(states_pos, 7u); + TEST_ASSERT_EQUALS(states[0], F4_START); + TEST_ASSERT_EQUALS(states[1], F5_START); + TEST_ASSERT_EQUALS(states[2], F5_INCR10); + TEST_ASSERT_EQUALS(states[3], F5_INCR20); + TEST_ASSERT_EQUALS(states[4], F5_INCR30); + TEST_ASSERT_EQUALS(states[5], F4_END); + TEST_ASSERT_EQUALS(states[6], F5_END); +} + + +void +FiberTest::testSleepFor() +{ + runSleepFor(16203); + runSleepFor(0xffff'ffff - 30); +} + +static void +f6() +{ + ADD_STATE(F6_START); + ADD_STATE(F6_SLEEP1); + modm::this_fiber::sleep_until(modm::Clock::now() + 50ms); + ADD_STATE(F6_SLEEP2); + modm::this_fiber::sleep_until(modm::Clock::now() - 50ms); + ADD_STATE(F6_END); +} + +static void +runSleepUntil(uint32_t startTime) +{ + test_clock::setTime(startTime); + states_pos = 0; + modm::fiber::Task fiber1(stack1, f6), fiber2(stack2, f5); + modm::fiber::Scheduler::run(); + + TEST_ASSERT_EQUALS(states_pos, 9u); + TEST_ASSERT_EQUALS(states[0], F6_START); + TEST_ASSERT_EQUALS(states[1], F6_SLEEP1); + TEST_ASSERT_EQUALS(states[2], F5_START); + TEST_ASSERT_EQUALS(states[3], F5_INCR10); + TEST_ASSERT_EQUALS(states[4], F5_INCR20); + TEST_ASSERT_EQUALS(states[5], F5_INCR30); + TEST_ASSERT_EQUALS(states[6], F6_SLEEP2); + TEST_ASSERT_EQUALS(states[7], F5_END); + TEST_ASSERT_EQUALS(states[8], F6_END); +} + +void +FiberTest::testSleepUntil() +{ + runSleepUntil(1502); + runSleepUntil(0xffff'ffff - 30); +} diff --git a/test/modm/processing/fiber/fiber_test.hpp b/test/modm/processing/fiber/fiber_test.hpp index d8b91fe686..7e80f923aa 100644 --- a/test/modm/processing/fiber/fiber_test.hpp +++ b/test/modm/processing/fiber/fiber_test.hpp @@ -17,10 +17,6 @@ class FiberTest : public unittest::TestSuite { public: - - void - subroutine(); - void testOneFiber(); @@ -29,4 +25,16 @@ class FiberTest : public unittest::TestSuite void testYieldFromSubroutine(); + + void + testPollFor(); + + void + testPollUntil(); + + void + testSleepFor(); + + void + testSleepUntil(); };