Skip to content

Commit

Permalink
[fiber] Implement sleep_for and sleep_until with polling
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Apr 20, 2024
1 parent 04961ab commit a0fc0f1
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 32 deletions.
87 changes: 72 additions & 15 deletions src/modm/processing/fiber/functions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "scheduler.hpp"
#include <modm/processing/timer.hpp>
#include <type_traits>

namespace modm::this_fiber
{
Expand Down Expand Up @@ -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<Rep, Period> interval)
template< class Rep, class Period, class Function >
[[nodiscard]] bool
poll_for(std::chrono::duration<Rep, Period> 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<Rep, Period>,
std::chrono::duration<Rep, std::milli>>,
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<Clock, Duration> 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<Rep, Period> 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<Clock, Duration> sleep_time)
{
(void) poll_until(sleep_time, [](){ return false; });
}

/// @}
Expand Down
146 changes: 133 additions & 13 deletions test/modm/processing/fiber/fiber_test.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Erik Henriksson
* Copyright (c) 2024, Niklas Hauser
*
* This file is part of the modm project.
*
Expand All @@ -14,9 +15,10 @@
#include <array>
#include <modm/debug/logger.hpp>
#include <modm/processing/fiber.hpp>
#include <modm-test/mock/clock.hpp>

namespace
{
using namespace std::chrono_literals;
using test_clock = modm_test::chrono::milli_clock;

enum State
{
Expand All @@ -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,
Expand All @@ -35,31 +48,27 @@ enum State
PRODUCER_END,
};

std::array<State, 6> states = {};
size_t states_pos = 0;

static std::array<State, 10> 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);
modm::this_fiber::yield();
ADD_STATE(F1_END);
}

void
static void
f2()
{
ADD_STATE(F2_START);
modm::this_fiber::yield();
ADD_STATE(F2_END);
}

modm::fiber::Stack<1024> stack1, stack2;

} // namespace

void
FiberTest::testOneFiber()
{
Expand All @@ -84,15 +93,15 @@ FiberTest::testTwoFibers()
TEST_ASSERT_EQUALS(states[3], F2_END);
}

__attribute__((noinline)) void
static __attribute__((noinline)) void
subroutine()
{
ADD_STATE(SUBROUTINE_START);
modm::this_fiber::yield();
ADD_STATE(SUBROUTINE_END);
}

void
static void
f3()
{
ADD_STATE(F3_START);
Expand All @@ -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);
}
16 changes: 12 additions & 4 deletions test/modm/processing/fiber/fiber_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
class FiberTest : public unittest::TestSuite
{
public:

void
subroutine();

void
testOneFiber();

Expand All @@ -29,4 +25,16 @@ class FiberTest : public unittest::TestSuite

void
testYieldFromSubroutine();

void
testPollFor();

void
testPollUntil();

void
testSleepFor();

void
testSleepUntil();
};

0 comments on commit a0fc0f1

Please sign in to comment.