Skip to content

Commit

Permalink
base: Alarm_registry data structure
Browse files Browse the repository at this point in the history
This data structure uses an AVL tree to maintain a time-sorted set of
alarm objects. It supports the use of circular clocks of an bit width.

Issue #5138
  • Loading branch information
nfeske authored and chelmuth committed Apr 12, 2024
1 parent 3e46cf5 commit 44e0d70
Show file tree
Hide file tree
Showing 11 changed files with 559 additions and 0 deletions.
1 change: 1 addition & 0 deletions repos/base/recipes/pkg/test-alarm/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Scenario that tests 'Genode::Alarm_registry'
2 changes: 2 additions & 0 deletions repos/base/recipes/pkg/test-alarm/archives
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_/src/init
_/src/test-alarm
1 change: 1 addition & 0 deletions repos/base/recipes/pkg/test-alarm/hash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2024-03-13 c282e266e79dc12297300db0f9eddbfff3d83d1c
23 changes: 23 additions & 0 deletions repos/base/recipes/pkg/test-alarm/runtime
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<runtime ram="32M" caps="1000" binary="test-alarm">

<fail after_seconds="20"/>
<succeed>
[init] in range [1...3]: a1
[init] in range [1...3]: a2
[init] in range [1...3]: a3
[init] in range [3...1]: a3
[init] in range [3...1]: a4
[init] in range [3...1]: a0
[init] in range [3...1]: a1*
[init] soonest(5) -> 0*
[init] Test succeeded.
</succeed>

<content>
<rom label="ld.lib.so"/>
<rom label="test-alarm"/>
</content>

<config/>

</runtime>
2 changes: 2 additions & 0 deletions repos/base/recipes/src/test-alarm/content.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SRC_DIR = src/test/alarm src/include/base/internal
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
1 change: 1 addition & 0 deletions repos/base/recipes/src/test-alarm/hash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2024-03-13 89f82089c7e23c62627f04af81fdc19ee17bfad3
1 change: 1 addition & 0 deletions repos/base/recipes/src/test-alarm/used_apis
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
base
281 changes: 281 additions & 0 deletions repos/base/src/include/base/internal/alarm_registry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* \brief Registry of time-sorted alarms
* \author Norman Feske
* \date 2024-03-06
*/

/*
* Copyright (C) 2024 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/

#ifndef _INCLUDE__BASE__INTERNAL__ALARM_REGISTRY_H_
#define _INCLUDE__BASE__INTERNAL__ALARM_REGISTRY_H_

/* Genode includes */
#include <base/log.h>

namespace Genode { template <typename, typename> class Alarm_registry; }


/**
* Registry of schedules alarm objects
*
* \param T alarm type, must be derived from 'Alarm_registry::Element'
* \param CLOCK type representing a circular clock
*
* The registry represents a set of scheduled alarms. An alarm object is
* scheduled upon creation and de-scheduled on destruction.
*
* The 'CLOCK' type must be constructible with an '<unsigned>' numeric value
* where '<unsigned>' can be an unsigned integer of any byte width.
* The clock provides the following interface:
*
* ! static constexpr <unsigned> MASK = <lsb-bitmask>;
* ! <unsigned> value() const;
* ! void print(Output &) const;
*
* 'MASK' defines the limit of the circular clock.
* The 'value()' method returns a number between 0 and MASK.
* The 'print' method is needed only when using 'Alarm_registry::print'.
* In this case, the alarm type must also provide a 'print' method.
*/
template <typename T, typename CLOCK>
class Genode::Alarm_registry : Noncopyable
{
private:

using Clock = CLOCK;

struct Range
{
Clock start, end; /* range [start,end] where 'start' >= 'end' */

void with_intersection(Range const other, auto const &fn) const
{
auto const f = max(start.value(), other.start.value());
auto const t = min(end.value(), other.end.value());

if (f <= t)
fn(Range { Clock { f }, Clock { t } });
}

bool contains(Clock const time) const
{
return (time.value() >= start.value())
&& (time.value() <= end.value());
}

void print(Output &out) const
{
Genode::print(out, "[", start.value(), "...", end.value(), "]");
}
};

public:

struct None { };
using Soonest_result = Attempt<Clock, None>;

class Element : Avl_node<Element>
{
private:

Alarm_registry &_registry;

T &_obj;

friend class Alarm_registry;
friend class Avl_node<Element>;
friend class Avl_tree<Element>;

public:

Clock time;

Element(Alarm_registry &registry, T &obj, Clock time)
:
_registry(registry), _obj(obj), time(time)
{
_registry._elements.insert(this);
}

~Element()
{
_registry._elements.remove(this);
}

/**
* Avl_node ordering, allow duplicated keys
*/
bool higher(Element const * const other) const
{
return time.value() <= other->time.value();
}

void print(Output &out) const
{
Genode::print(out, _obj, ": time=", time);
}

private:

static void _with_child(auto &element, bool side, auto const &fn)
{
if (element.child(side))
fn(*element.child(side));
}

void _for_each(Range const range, auto const &fn) const
{
_with_child(*this, this->LEFT, [&] (Element const &child) {
range.with_intersection({ Clock { }, time }, [&] (Range l_range) {
child._for_each(l_range, fn); }); });

if (range.contains(time))
fn(_obj);

_with_child(*this, this->RIGHT, [&] (Element const &child) {
range.with_intersection({ time, Clock { Clock::MASK }}, [&] (Range r_range) {
child._for_each(r_range, fn); }); });
}

Element *_find_any(Range const range)
{
if (range.contains(time))
return this;

Element *result = nullptr;

_with_child(*this, this->LEFT, [&] (Element &child) {
range.with_intersection({ Clock { }, time }, [&] (Range l_range) {
result = child._find_any(l_range); }); });

if (result)
return result;

_with_child(*this, this->RIGHT, [&] (Element &child) {
range.with_intersection({ time, Clock { Clock::MASK }}, [&] (Range r_range) {
result = child._find_any(r_range); }); });

return result;
}

Soonest_result _soonest(Clock const now) const
{
Soonest_result result = None { };

if (time.value() >= now.value()) {
result = time;
_with_child(*this, this->LEFT, [&] (Element const &child) {
child._soonest(now).with_result(
[&] (Clock left_soonest) {
if (time.value() > left_soonest.value())
result = left_soonest; },
[&] (None) { }); });
} else {
_with_child(*this, this->RIGHT, [&] (Element const &child) {
result = child._soonest(now); });
}

return result;
}
};

private:

Avl_tree<Element> _elements { };

static void _with_first(auto &registry, auto const &fn)
{
if (registry._elements.first())
fn(*registry._elements.first());
}

/*
* Call 'fn' up to two times, with the first element and a search range
* as argument.
*/
static void _for_each_search_range(auto &registry, Clock start, Clock end,
auto const &fn)
{
_with_first(registry, [&] (auto &first) {

if (start.value() <= end.value()) {
fn(first, Range { start, end });

} else if (start.value() > end.value()) {
fn(first, Range { start, Clock { Clock::MASK } });
fn(first, Range { Clock { }, end });
}
});
}

public:

/**
* Return soonest alarm time from 'now'
*/
Soonest_result soonest(Clock const now) const
{
Soonest_result result = None { };

_with_first(*this, [&] (auto &first) {
first._soonest(now).with_result(
[&] (Clock soonest) {
result = soonest; },
[&] (None) { /* clock wrapped, search from beginning */
result = first._soonest(Clock { }); }); });

return result;
}

/**
* Call 'fn' for each alarm scheduled between 'start' and 'end'
*
* The 'start' and 'end' values may wrap.
*/
void for_each_in_range(Clock const start, Clock const end, auto const &fn) const
{
_for_each_search_range(*this, start, end, [&] (Element const &e, Range range) {
e._for_each(range, fn); });
}

/**
* Call 'fn' with any alarm scheduled between 'start' and 'end'
*
* \return true if 'fn' was called
*
* The found alarm is passed to 'fn' as non-const reference, which
* allows the caller to modify or destroy it.
*
* The return value is handy for calling 'with_any_in_range' as a
* condition of a while loop, purging all alarms within the time
* window.
*/
bool with_any_in_range(Clock const start, Clock const end, auto const &fn)
{
Element *found_ptr = nullptr;
_for_each_search_range(*this, start, end, [&] (Element &e, Range range) {
if (!found_ptr)
found_ptr = e._find_any(range); });

if (found_ptr)
fn(found_ptr->_obj);

return found_ptr != nullptr;
}

void print(Output &out) const
{
bool first = true;
for_each_in_range(Clock { }, Clock { Clock::MASK }, [&] (Element const &e) {
Genode::print(out, first ? "" : "\n", e);
first = false;
});
}
};

#endif /* _INCLUDE__BASE__INTERNAL__ALARM_REGISTRY_H_ */
Loading

0 comments on commit 44e0d70

Please sign in to comment.