sandbox/init: parse config using 'List_model'

This commit replaces the hand-crafted config processing by the use of
the 'List_model' utility. This has the following advantages:

- The parsing follows a common formalism that makes the code
  easier to maintain and to understand. Several parts of the code
  had to be changed (for the better) to make it fit the list model
  approach. E.g., the child states have become more expressive
  and logical.

- In the common case, the XML data is traversed only once, which
  increases the parsing speed in dynamic scenarios.

- The code becomes easier to optimize. In particular, the patch skips
  the re-evaluation of the session routing if no service is affected
  by the config change.

The patch also revisits the init test by removing overly long sleep
phases and extending a few sleep phases that were too short when
executing the test on Qemu.

Issue #4068
This commit is contained in:
Norman Feske 2021-04-01 18:05:23 +02:00
parent f925fef17b
commit b661459aca
18 changed files with 1309 additions and 531 deletions

View File

@ -1,4 +1,4 @@
SRC_CC = library.cc child.cc server.cc
SRC_CC = library.cc child.cc server.cc config_model.cc
INC_DIR += $(REP_DIR)/src/lib/sandbox
LIBS += base

View File

@ -64,7 +64,7 @@
</start>
</init_config>
<expect_log string="[init -> exiting] started"/>
<sleep ms="200"/>
<sleep ms="350"/>
<expect_init_state>
<attribute name="version" value="exiting child"/>
<node name="child">
@ -261,7 +261,7 @@
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<sleep ms="150"/>
<sleep ms="300"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="indirect_server"/>
@ -306,7 +306,7 @@
</start>
</init_config>
<expect_log string="[init -> log] no service yet"/>
<sleep ms="1000"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="log"/> </node>
<node name="child">
@ -524,6 +524,7 @@
</start>
</init_config>
<expect_log string="[init -> test] config 1: binary re-routed to other route"/>
<sleep ms="100"/>
<message string="test RAM preservation"/>
@ -551,7 +552,7 @@
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<sleep ms="1000"/>
<sleep ms="300"/>
<!-- wait until both children are started -->
<expect_init_state>
<node name="child"> <attribute name="name" value="regular"/> </node>
@ -759,7 +760,7 @@
</init_config>
<expect_log string="[init -> test] config 7: consume 2 MiB"/>
<expect_log string="[init -> test] consume 2M bytes of memory"/>
<sleep ms="1000"/>
<sleep ms="300"/>
<!-- activate child_ram report -->
<init_config version="report consumed">
<report child_ram="yes"/>
@ -838,7 +839,7 @@
</start>
</init_config>
<expect_log string="[init -> test] config 8: request more quota than avail"/>
<sleep ms="2000"/>
<sleep ms="200"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="ram">
@ -1078,7 +1079,7 @@
</init_config>
<expect_log string="[init -> test] config 7: consume 100 caps"/>
<expect_log string="[init -> test] consume 100 caps"/>
<sleep ms="1000"/>
<sleep ms="150"/>
<!-- activate child_caps report -->
<init_config version="report consumed">
@ -1155,7 +1156,7 @@
</start>
</init_config>
<expect_log string="[init -> test] config 8: request more quota than avail"/>
<sleep ms="2000"/>
<sleep ms="300"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="test"/>
<node name="caps">
@ -1246,6 +1247,7 @@
</route>
</start>
</init_config>
<sleep ms="500"/>
<expect_init_state>
<attribute name="version" value="session forwarding"/>
<node name="child">
@ -1266,6 +1268,7 @@
good-case tests. -->
<init_config version="empty"> <report/> </init_config>
<sleep ms="150"/>
<expect_init_state>
<attribute name="version" value="empty"/>
</expect_init_state>
@ -1478,11 +1481,11 @@
<expect_init_state>
<node name="child">
<attribute name="name" value="server"/>
<attribute name="id" value="27"/>
<attribute name="id" value="26"/>
</node>
<node name="child">
<attribute name="name" value="client"/>
<attribute name="id" value="28"/>
<attribute name="id" value="27"/>
</node>
</expect_init_state>
<sleep ms="150"/>
@ -1513,7 +1516,7 @@
<expect_init_state>
<node name="child">
<attribute name="name" value="client"/>
<attribute name="id" value="29"/>
<attribute name="id" value="28"/>
</node>
</expect_init_state>
<sleep ms="150"/>
@ -1543,7 +1546,7 @@
</start>
</init_config>
<expect_log string="[init -> flaky] started"/>
<sleep ms="1000"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="flaky"/>
@ -1657,8 +1660,8 @@
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_warning string="[init] " colored="Warning: affinity-space configuration missing, but affinity defined for child: affinity"/>
<sleep ms="150"/>
<expect_warning string="[init] " colored="Warning: affinity-space configuration missing, but affinity defined for child affinity"/>
<sleep ms="350"/>
<message string="destroy child while async close requested"/>
@ -1682,7 +1685,7 @@
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service delay_close_ms="500"/> </config>
<config> <log_service delay_close_ms="750"/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="client">
@ -1701,7 +1704,7 @@
</route>
</start>
</init_config>
<sleep ms="300"/>
<sleep ms="600"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="server"/>
@ -1730,7 +1733,7 @@
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<sleep ms="500"/>
<sleep ms="750"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="server"/>

View File

@ -6,6 +6,6 @@ INC_DIR += $(PRG_DIR)
CONFIG_XSD = config.xsd
# statically link sandbox library to avoid dependency from sandbox.lib.so
SRC_CC += library.cc child.cc server.cc
SRC_CC += library.cc child.cc server.cc config_model.cc
INC_DIR += $(REP_DIR)/src/lib/sandbox
vpath %.cc $(REP_DIR)/src/lib/sandbox

View File

@ -19,34 +19,28 @@
namespace Sandbox { struct Alias; }
struct Sandbox::Alias : List<Alias>::Element
struct Sandbox::Alias : List<Alias>::Element, Noncopyable
{
typedef String<128> Name;
typedef String<128> Child;
typedef Child_policy::Name Name;
typedef Child_policy::Name Child;
Name name;
Child child;
Name const name;
/**
* Exception types
Child child { }; /* defined by 'update' */
Alias(Name const &name) : name(name) { }
class Child_attribute_missing : Exception { };
/*
* \throw Child_attribute_missing
*/
class Name_is_missing : Exception { };
class Child_is_missing : Exception { };
/**
* Constructor
*
* \throw Name_is_missing
* \throw Child_is_missing
*/
Alias(Genode::Xml_node alias)
:
name (alias.attribute_value("name", Name())),
child(alias.attribute_value("child", Child()))
void update(Xml_node const &alias)
{
if (!alias.has_attribute("child"))
warning("alias node \"", name, "\" lacks child attribute");
if (!name.valid()) throw Name_is_missing();
if (!child.valid()) throw Child_is_missing();
child = alias.attribute_value("child", Child());
}
};

View File

@ -11,6 +11,7 @@
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <vm_session/vm_session.h>
/* local includes */
@ -28,14 +29,14 @@ void Sandbox::Child::destroy_services()
Sandbox::Child::Apply_config_result
Sandbox::Child::apply_config(Xml_node start_node)
{
if (_state == STATE_ABANDONED || _exited)
if (abandoned() || stuck() || restart_scheduled() || _exited)
return NO_SIDE_EFFECTS;
/*
* If the child's environment is incomplete, restart it to attempt
* the re-routing of its environment sessions.
* If the child was started but its environment is incomplete, mark it as
* being stuck in order to restart it once the environment changes.
*/
{
if (_state != State::INITIAL) {
bool env_log_exists = false, env_binary_exists = false;
_child.for_each_session([&] (Session_state const &session) {
Parent::Client::Id const id = session.id_at_client();
@ -44,8 +45,8 @@ Sandbox::Child::apply_config(Xml_node start_node)
});
if (!env_binary_exists || !env_log_exists) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
_state = State::STUCK;
return NO_SIDE_EFFECTS;
}
}
@ -62,14 +63,24 @@ Sandbox::Child::apply_config(Xml_node start_node)
if (start_node.differs_from(_start_node->xml())) {
/*
* Start node changed
*
* The <route> node may affect the availability or unavailability
* of dependencies.
*/
start_node.with_sub_node("route", [&] (Xml_node const &route) {
_start_node->xml().with_sub_node("route", [&] (Xml_node const &orig) {
if (route.differs_from(orig))
_uncertain_dependencies = true; }); });
/*
* Determine how the inline config is affected.
*/
char const * const tag = "config";
bool const config_was_present = _start_node->xml().has_sub_node(tag);
bool const config_is_present = start_node.has_sub_node(tag);
if (config_was_present != config_is_present)
_uncertain_dependencies = true;
if (config_was_present && !config_is_present)
config_update = CONFIG_VANISHED;
@ -125,7 +136,10 @@ Sandbox::Child::apply_config(Xml_node start_node)
* the binary's ROM session, triggering the restart of the
* child.
*/
Binary_name const orig_binary_name = _binary_name;
_binary_name = _binary_from_xml(start_node, _unique_name);
if (orig_binary_name != _binary_name)
_uncertain_dependencies = true;
_heartbeat_enabled = start_node.has_sub_node("heartbeat");
@ -136,8 +150,8 @@ Sandbox::Child::apply_config(Xml_node start_node)
/*
* Apply change to '_config_rom_service'. This will
* potentially result in a change of the "config" ROM route, which
* may in turn prompt the routing-check below to abandon (restart)
* the child.
* may in turn prompt the routing-check by 'evaluate_dependencies'
* to restart the child.
*/
switch (config_update) {
case CONFIG_UNCHANGED: break;
@ -146,26 +160,39 @@ Sandbox::Child::apply_config(Xml_node start_node)
case CONFIG_VANISHED: _config_rom_service->abandon(); break;
}
/* validate that the routes of all existing sessions remain intact */
{
bool routing_changed = false;
_child.for_each_session([&] (Session_state const &session) {
if (!_route_valid(session))
routing_changed = true; });
if (routing_changed) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
}
}
if (provided_services_changed)
return MAY_HAVE_SIDE_EFFECTS;
return PROVIDED_SERVICES_CHANGED;
return NO_SIDE_EFFECTS;
}
void Sandbox::Child::evaluate_dependencies()
{
bool any_route_changed = false;
bool any_route_unavailable = false;
_child.for_each_session([&] (Session_state const &session) {
switch (_route_valid(session)) {
case Route_state::VALID: break;
case Route_state::UNAVAILABLE: any_route_unavailable = true; break;
case Route_state::MISMATCH: any_route_changed = true; break;
}
});
_uncertain_dependencies = false;
if (any_route_unavailable) {
_state = State::STUCK;
return;
}
if (any_route_changed || stuck())
_schedule_restart();
}
Sandbox::Ram_quota Sandbox::Child::_configured_ram_quota() const
{
size_t assigned = 0;
@ -338,7 +365,7 @@ void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detai
if (detail.ids())
xml.attribute("id", _id.value);
if (!_child.active())
if (stuck() || _state == State::RAM_INITIALIZED)
xml.attribute("state", "incomplete");
if (_exited)
@ -420,11 +447,29 @@ void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap)
size_t const initial_session_costs =
session_alloc_batch_size()*_child.session_factory().session_costs();
Ram_quota const ram_quota { _resources.effective_ram_quota().value > initial_session_costs
Ram_quota ram_quota { _resources.effective_ram_quota().value > initial_session_costs
? _resources.effective_ram_quota().value - initial_session_costs
: 0 };
Cap_quota const cap_quota { _resources.effective_cap_quota().value };
Ram_quota avail_ram = _ram_limit_accessor.resource_limit(Ram_quota());
avail_ram = Genode::Child::effective_quota(avail_ram);
if (ram_quota.value > avail_ram.value) {
warning(name(), ": configured RAM exceeds available RAM, proceed with ", avail_ram);
ram_quota = avail_ram;
}
Cap_quota cap_quota { _resources.effective_cap_quota().value };
Cap_quota avail_caps = _cap_limit_accessor.resource_limit(avail_caps);
avail_caps = Genode::Child::effective_quota(avail_caps);
if (cap_quota.value > avail_caps.value) {
warning(name(), ": configured caps exceed available caps, proceed with ", avail_caps);
cap_quota = avail_caps;
}
try { _env.pd().transfer_quota(cap, cap_quota); }
catch (Out_of_caps) {
@ -718,8 +763,6 @@ Sandbox::Child::Child(Env &env,
Default_route_accessor &default_route_accessor,
Default_caps_accessor &default_caps_accessor,
Name_registry &name_registry,
Ram_quota ram_limit,
Cap_quota cap_limit,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Prio_levels prio_levels,
@ -739,8 +782,7 @@ Sandbox::Child::Child(Env &env,
_name_registry(name_registry),
_heartbeat_enabled(start_node.has_sub_node("heartbeat")),
_resources(_resources_from_start_node(start_node, prio_levels, affinity_space,
default_caps_accessor.default_caps(), cap_limit)),
_resources_clamped_to_limit((_clamp_resources(ram_limit, cap_limit), true)),
default_caps_accessor.default_caps())),
_parent_services(parent_services),
_child_services(child_services),
_local_services(local_services),

View File

@ -78,10 +78,34 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Id const _id;
enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE,
STATE_ABANDONED };
enum class State {
State _state = STATE_INITIAL;
/*
* States modelling the child's boostrap phase
*/
INITIAL, RAM_INITIALIZED, ALIVE,
/*
* The child is present in the config model but its bootstrapping
* permanently failed.
*/
STUCK,
/*
* The child must be restarted because a fundamental dependency
* changed. While the child is in this state, it is still
* referenced by the config model.
*/
RESTART_SCHEDULED,
/*
* The child is no longer referenced by config model and can
* safely be destructed.
*/
ABANDONED
};
State _state = State::INITIAL;
Report_update_trigger &_report_update_trigger;
@ -94,6 +118,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
*/
Version _version { _start_node->xml().attribute_value("version", Version()) };
bool _uncertain_dependencies = false;
/*
* True if the binary is loaded with ld.lib.so
*/
@ -117,7 +143,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
if (name.valid())
return name;
warning("missint 'name' attribute in '<start>' entry");
warning("missing 'name' attribute in '<start>' entry");
throw Missing_name_attribute();
}
@ -151,7 +177,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
bool _heartbeat_expected() const
{
/* don't expect heartbeats from a child that is not yet complete */
return _heartbeat_enabled && (_state == STATE_ALIVE);
return _heartbeat_enabled && (_state == State::ALIVE);
}
/**
@ -193,9 +219,10 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
}
};
static
Resources _resources_from_start_node(Xml_node start_node, Prio_levels prio_levels,
Affinity::Space const &affinity_space,
Cap_quota default_cap_quota, Cap_quota)
Cap_quota default_cap_quota)
{
size_t cpu_quota_pc = 0;
Number_of_bytes ram_bytes = 0;
@ -231,29 +258,9 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Resources _resources;
/**
* Print diagnostic information on misconfiguration
*/
void _clamp_resources(Ram_quota ram_limit, Cap_quota cap_limit)
{
if (_resources.assigned_ram_quota.value > ram_limit.value) {
warning(name(), " assigned RAM (", _resources.assigned_ram_quota, ") "
"exceeds available RAM (", ram_limit, ")");
_resources.assigned_ram_quota = ram_limit;
}
if (_resources.assigned_cap_quota.value > cap_limit.value) {
warning(name(), " assigned caps (", _resources.assigned_cap_quota, ") "
"exceed available caps (", cap_limit, ")");
_resources.assigned_cap_quota = cap_limit;
}
}
Ram_quota _configured_ram_quota() const;
Cap_quota _configured_cap_quota() const;
bool const _resources_clamped_to_limit;
using Local_service = Genode::Sandbox::Local_service_base;
Registry<Parent_service> &_parent_services;
@ -375,6 +382,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
_session_requester.trigger_update();
}
enum class Route_state { VALID, MISMATCH, UNAVAILABLE };
/**
* Return true if the policy results in the current route of the session
*
@ -382,7 +391,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
* client session of a child, i.e., to determine whether the child must
* be restarted.
*/
bool _route_valid(Session_state const &session)
Route_state _route_valid(Session_state const &session)
{
try {
Route const route =
@ -390,10 +399,12 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
session.client_label(),
session.diag());
return (session.service() == route.service)
bool const valid = (session.service() == route.service)
&& (route.label == session.label());
return valid ? Route_state::VALID : Route_state::MISMATCH;
}
catch (Service_denied) { return false; }
catch (Service_denied) { return Route_state::UNAVAILABLE; }
}
static Xml_node _provides_sub_node(Xml_node start_node)
@ -421,7 +432,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
service.name() == node.attribute_value("name", Service::Name()))
exists = true; });
return exists && !abandoned();
return exists && !abandoned() && !restart_scheduled();
}
void _add_service(Xml_node service)
@ -454,7 +465,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
*/
bool _pd_alive() const
{
return !abandoned() && !_exited;
return !abandoned() && !restart_scheduled() && !_exited;
}
void _destroy_services();
@ -477,6 +488,19 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
} _sampled_state { };
void _abandon_services()
{
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
service.abandon(); });
}
void _schedule_restart()
{
_state = State::RESTART_SCHEDULED;
_abandon_services();
}
public:
/**
@ -505,8 +529,6 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Default_route_accessor &default_route_accessor,
Default_caps_accessor &default_caps_accessor,
Name_registry &name_registry,
Ram_quota ram_limit,
Cap_quota cap_limit,
Ram_limit_accessor &ram_limit_accessor,
Cap_limit_accessor &cap_limit_accessor,
Prio_levels prio_levels,
@ -527,50 +549,55 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
Ram_quota ram_quota() const { return _resources.assigned_ram_quota; }
Cap_quota cap_quota() const { return _resources.assigned_cap_quota; }
void initiate_env_pd_session()
void try_start()
{
if (_state == STATE_INITIAL) {
if (_state == State::INITIAL) {
_child.initiate_env_pd_session();
_state = STATE_RAM_INITIALIZED;
}
_state = State::RAM_INITIALIZED;
}
void initiate_env_sessions()
{
if (_state == STATE_RAM_INITIALIZED) {
/*
* Update the state if async env sessions have brought the child to
* life. Otherwise, we would wrongly call 'initiate_env_sessions()'
* another time.
*/
if (_state == State::RAM_INITIALIZED && _child.active())
_state = State::ALIVE;
if (_state == State::RAM_INITIALIZED) {
_child.initiate_env_sessions();
/* check for completeness of the child's environment */
if (_verbose.enabled())
_child.for_each_session([&] (Session_state const &session) {
if (!session.alive())
warning(name(), ": incomplete environment ",
session.service().name(), " session "
"(", session.label(), ")"); });
_state = STATE_ALIVE;
if (_child.active())
_state = State::ALIVE;
else
_uncertain_dependencies = true;
}
}
/*
* Mark child as to be removed because its was dropped from the
* config model. Either <start> node disappeared or 'restart_scheduled'
* was handled.
*/
void abandon()
{
_state = STATE_ABANDONED;
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
service.abandon(); });
_state = State::ABANDONED;
_abandon_services();
}
void destroy_services();
void close_all_sessions() { _child.close_all_sessions(); }
bool abandoned() const { return _state == STATE_ABANDONED; }
bool abandoned() const { return _state == State::ABANDONED; }
bool restart_scheduled() const { return _state == State::RESTART_SCHEDULED; }
bool stuck() const { return _state == State::STUCK; }
bool env_sessions_closed() const { return _child.env_sessions_closed(); }
enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS };
enum Apply_config_result { PROVIDED_SERVICES_CHANGED, NO_SIDE_EFFECTS };
/**
* Apply new configuration to child
@ -580,6 +607,15 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup
*/
Apply_config_result apply_config(Xml_node start_node);
bool uncertain_dependencies() const { return _uncertain_dependencies; }
/**
* Validate that the routes of all existing sessions remain intact
*
* The child may become scheduled for restart or get stuck.
*/
void evaluate_dependencies();
/* common code for upgrading RAM and caps */
template <typename QUOTA, typename LIMIT_ACCESSOR>
void _apply_resource_upgrade(QUOTA &, QUOTA, LIMIT_ACCESSOR const &);

View File

@ -29,30 +29,8 @@ class Sandbox::Child_registry : public Name_registry, Child_list
List<Alias> _aliases { };
bool _unique(const char *name) const
{
/* check for name clash with an existing child */
List_element<Child> const *curr = first();
for (; curr; curr = curr->next())
if (curr->object()->has_name(name))
return false;
/* check for name clash with an existing alias */
for (Alias const *a = _aliases.first(); a; a = a->next()) {
if (Alias::Name(name) == a->name)
return false;
}
return true;
}
public:
/**
* Exception type
*/
class Alias_name_is_not_unique { };
/**
* Register child
*/
@ -74,10 +52,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list
*/
void insert_alias(Alias *alias)
{
if (!_unique(alias->name.string())) {
error("alias name ", alias->name, " is not unique");
throw Alias_name_is_not_unique();
}
_aliases.insert(alias);
}
@ -89,22 +63,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list
_aliases.remove(alias);
}
/**
* Return any of the registered children, or 0 if no child exists
*/
Child *any()
{
return first() ? first()->object() : 0;
}
/**
* Return any of the registered aliases, or 0 if no alias exists
*/
Alias *any_alias()
{
return _aliases.first() ? _aliases.first() : 0;
}
template <typename FN>
void for_each_child(FN const &fn) const
{
@ -127,7 +85,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list
{
for_each_child([&] (Child &child) { child.report_state(xml, detail); });
/* check for name clash with an existing alias */
for (Alias const *a = _aliases.first(); a; a = a->next()) {
xml.node("alias", [&] () {
xml.attribute("name", a->name);

View File

@ -0,0 +1,402 @@
/*
* \brief Internal model of the XML configuration
* \author Norman Feske
* \date 2021-04-02
*/
/*
* Copyright (C) 2021 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.
*/
#include <config_model.h>
using namespace Sandbox;
struct Config_model::Node : Noncopyable, Interface, private List_model<Node>::Element
{
friend class List_model<Node>;
friend class List<Node>;
virtual bool matches(Xml_node const &) const = 0;
virtual void update(Xml_node const &) = 0;
virtual void apply_child_restart(Xml_node const &) { /* only implemented by 'Start_node' */ }
virtual void trigger_start_child() { /* only implemented by 'Start_node' */ }
};
struct Config_model::Parent_provides_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("parent-provides");
}
Parent_provides_model _model;
Parent_provides_node(Allocator &alloc, Verbose const &verbose,
Parent_provides_model::Factory &factory)
:
_model(alloc, verbose, factory)
{ }
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override { _model.update_from_xml(xml); }
};
struct Config_model::Default_route_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("default-route");
}
Allocator &_alloc;
Constructible<Buffered_xml> &_default_route;
Default_route_node(Allocator &alloc, Constructible<Buffered_xml> &default_route)
: _alloc(alloc), _default_route(default_route) { }
~Default_route_node() { _default_route.destruct(); }
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override
{
if (!_default_route.constructed() || _default_route->xml().differs_from(xml))
_default_route.construct(_alloc, xml);
}
};
struct Config_model::Default_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("default");
}
Cap_quota &_default_caps;
Default_node(Cap_quota &default_caps) : _default_caps(default_caps) { }
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override
{
_default_caps = Cap_quota { xml.attribute_value("caps", 0UL) };
}
};
struct Config_model::Affinity_space_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("affinity-space");
}
Constructible<Affinity::Space> &_affinity_space;
Affinity_space_node(Constructible<Affinity::Space> &affinity_space)
: _affinity_space(affinity_space) { }
~Affinity_space_node() { _affinity_space.destruct(); }
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override
{
_affinity_space.construct(xml.attribute_value("width", 1u),
xml.attribute_value("height", 1u));
}
};
struct Config_model::Start_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("start") || xml.has_type("alias");
}
Start_model _model;
/*
* \throw Start_model::Factory::Creation_failed
*/
Start_node(Start_model::Factory &factory, Xml_node const &xml)
: _model(factory, xml) { }
bool matches(Xml_node const &xml) const override
{
return type_matches(xml) && _model.matches(xml);
}
void update(Xml_node const &xml) override
{
_model.update_from_xml(xml);
}
void apply_child_restart(Xml_node const &xml) override
{
_model.apply_child_restart(xml);
}
void trigger_start_child() override
{
_model.trigger_start_child();
}
};
struct Config_model::Report_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("report");
}
Version const &_version;
State_reporter &_state_reporter;
Report_node(Version const &version, State_reporter &state_reporter)
: _version(version), _state_reporter(state_reporter) { }
~Report_node()
{
_state_reporter.apply_config(_version, Xml_node("<empty/>"));
}
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override
{
_state_reporter.apply_config(_version, xml);
}
};
struct Config_model::Resource_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("resource");
}
enum class Category { RAM, CAP } const _category;
class Unknown_resource_name : Exception { };
static Category _category_from_xml(Xml_node const &xml)
{
typedef String<16> Name;
Name const name = xml.attribute_value("name", Name());
if (name == "RAM") return Category::RAM;
if (name == "CAP") return Category::CAP;
throw Unknown_resource_name();
}
Preservation &_keep;
/*
* \throw Unknown_resource_name
*/
Resource_node(Preservation &keep, Xml_node xml)
:
_category(_category_from_xml(xml)), _keep(keep)
{ }
~Resource_node()
{
switch (_category) {
case Category::RAM: _keep.ram = Preservation::default_ram(); break;
case Category::CAP: _keep.caps = Preservation::default_caps(); break;
}
}
bool matches(Xml_node const &xml) const override
{
return type_matches(xml) && _category == _category_from_xml(xml);
}
void update(Xml_node const &xml) override
{
switch (_category) {
case Category::RAM:
{
Number_of_bytes keep { Preservation::default_ram().value };
_keep.ram = { xml.attribute_value("preserve", keep) };
break;
}
case Category::CAP:
{
size_t keep = Preservation::default_caps().value;
_keep.caps = { xml.attribute_value("preserve", keep) };
break;
}
}
}
};
struct Config_model::Heartbeat_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("heartbeat");
}
Heartbeat &_heartbeat;
Heartbeat_node(Heartbeat &heartbeat) : _heartbeat(heartbeat) { }
~Heartbeat_node()
{
_heartbeat.disable();
}
bool matches(Xml_node const &xml) const override { return type_matches(xml); }
void update(Xml_node const &xml) override
{
_heartbeat.apply_config(xml);
}
};
struct Config_model::Service_node : Node
{
static bool type_matches(Xml_node const &xml)
{
return xml.has_type("service");
}
Service_model::Factory &_factory;
Service_model &_model;
Service_node(Service_model::Factory &factory, Xml_node const &xml)
: _factory(factory), _model(factory.create_service(xml)) { }
~Service_node() { _factory.destroy_service(_model); }
bool matches(Xml_node const &xml) const override
{
return type_matches(xml) && _model.matches(xml);
}
void update(Xml_node const &xml) override { _model.update_from_xml(xml); }
};
void Config_model::update_from_xml(Xml_node const &xml,
Allocator &alloc,
Reconstructible<Verbose> &verbose,
Version &version,
Preservation &preservation,
Constructible<Buffered_xml> &default_route,
Cap_quota &default_caps,
Prio_levels &prio_levels,
Constructible<Affinity::Space> &affinity_space,
Start_model::Factory &child_factory,
Parent_provides_model::Factory &parent_service_factory,
Service_model::Factory &service_factory,
State_reporter &state_reporter,
Heartbeat &heartbeat)
{
/* config version to be reflected in state reports */
version = xml.attribute_value("version", Version());
preservation.reset();
prio_levels = ::Sandbox::prio_levels_from_xml(xml);
affinity_space.destruct();
verbose.construct(xml);
class Unknown_element_type : Exception { };
auto destroy = [&] (Node &node) { Genode::destroy(alloc, &node); };
auto create = [&] (Xml_node const &xml) -> Node &
{
if (Parent_provides_node::type_matches(xml))
return *new (alloc)
Parent_provides_node(alloc, *verbose, parent_service_factory);
if (Default_route_node::type_matches(xml))
return *new (alloc) Default_route_node(alloc, default_route);
if (Default_node::type_matches(xml))
return *new (alloc) Default_node(default_caps);
if (Start_node::type_matches(xml))
return *new (alloc) Start_node(child_factory, xml);
if (Affinity_space_node::type_matches(xml))
return *new (alloc) Affinity_space_node(affinity_space);
if (Report_node::type_matches(xml))
return *new (alloc) Report_node(version, state_reporter);
if (Resource_node::type_matches(xml))
return *new (alloc) Resource_node(preservation, xml);
if (Heartbeat_node::type_matches(xml))
return *new (alloc) Heartbeat_node(heartbeat);
if (Service_node::type_matches(xml))
return *new (alloc) Service_node(service_factory, xml);
error("unknown config element type <", xml.type(), ">");
throw Unknown_element_type();
};
auto update = [&] (Node &node, Xml_node const &xml) { node.update(xml); };
try {
update_list_model_from_xml(_model, xml, create, destroy, update);
}
catch (Unknown_element_type) {
error("unable to apply complete configuration"); }
catch (Start_model::Factory::Creation_failed) {
error("child creation failed"); }
}
void Config_model::apply_children_restart(Xml_node const &xml)
{
class Unexpected : Exception { };
auto destroy = [&] (Node &) { };
auto create = [&] (Xml_node const &) -> Node & { throw Unexpected(); };
auto update = [&] (Node &node, Xml_node const &xml)
{
node.apply_child_restart(xml);
};
try {
update_list_model_from_xml(_model, xml, create, destroy, update);
}
catch (...) { };
}
void Config_model::trigger_start_children()
{
_model.for_each([&] (Node &node) {
node.trigger_start_child(); });
}

View File

@ -0,0 +1,283 @@
/*
* \brief Internal model of the XML configuration
* \author Norman Feske
* \date 2021-04-01
*/
/*
* Copyright (C) 2021 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 _CONFIG_MODEL_H_
#define _CONFIG_MODEL_H_
/* local includes */
#include <update_list_model.h>
#include <heartbeat.h>
namespace Sandbox {
struct Parent_provides_model;
struct Start_model;
struct Service_model;
struct Config_model;
}
struct Sandbox::Parent_provides_model : Noncopyable
{
struct Factory : Interface, Noncopyable
{
virtual Parent_service &create_parent_service(Service::Name const &) = 0;
};
Allocator &_alloc;
Verbose const &_verbose;
Factory &_factory;
struct Node : Noncopyable, List_model<Node>::Element
{
Parent_service &service;
Node(Factory &factory, Service::Name const &name)
:
service(factory.create_parent_service(name))
{ }
~Node()
{
/*
* The destruction of the 'Parent_service' is deferred to the
* handling of abandoned entries of the 'Registry<Parent_service>'.
*/
service.abandon();
}
bool matches(Xml_node const &xml) const
{
return xml.attribute_value("name", Service::Name()) == service.name();
};
};
List_model<Node> _model { };
Parent_provides_model(Allocator &alloc, Verbose const &verbose, Factory &factory)
:
_alloc(alloc), _verbose(verbose), _factory(factory)
{ }
void update_from_xml(Xml_node const &xml)
{
bool first_log = true;
auto create = [&] (Xml_node const &xml) -> Node &
{
Service::Name const name = xml.attribute_value("name", Service::Name());
if (_verbose.enabled()) {
if (first_log)
log("parent provides");
log(" service \"", name, "\"");
first_log = false;
}
return *new (_alloc) Node(_factory, name);
};
auto destroy = [&] (Node &node) { Genode::destroy(_alloc, &node); };
auto update = [&] (Node &, Xml_node const &) { };
try {
update_list_model_from_xml(_model, xml, create, destroy, update);
} catch (...) {
error("unable to apply complete configuration");
}
}
};
struct Sandbox::Start_model : Noncopyable
{
/*
* The 'Start_model' represents both '<alias>' nodes and '<start>' nodes
* because both node types share the same name space.
*/
typedef Child_policy::Name Name;
typedef Child::Version Version;
static char const *start_type() { return "start"; }
static char const *alias_type() { return "alias"; }
struct Factory : Interface
{
class Creation_failed : Exception { };
virtual bool ready_to_create_child(Name const &, Version const &) const = 0;
/*
* \throw Creation_failed
*/
virtual Child &create_child(Xml_node const &start) = 0;
virtual void update_child(Child &child, Xml_node const &start) = 0;
/*
* \throw Creation_failed
*/
virtual Alias &create_alias(Name const &) = 0;
virtual void destroy_alias(Alias &) = 0;
};
Name const _name;
Version const _version;
Factory &_factory;
bool _alias = false;
struct { Child *_child_ptr = nullptr; };
struct { Alias *_alias_ptr = nullptr; };
void _reset()
{
if (_child_ptr) _child_ptr->abandon();
if (_alias_ptr) _factory.destroy_alias(*_alias_ptr);
_child_ptr = nullptr;
_alias_ptr = nullptr;
}
bool matches(Xml_node const &xml) const
{
return _name == xml.attribute_value("name", Name())
&& _version == xml.attribute_value("version", Version());
}
Start_model(Factory &factory, Xml_node const &xml)
:
_name(xml.attribute_value("name", Name())),
_version(xml.attribute_value("version", Version())),
_factory(factory)
{ }
~Start_model() { _reset(); }
void update_from_xml(Xml_node const &xml)
{
/* handle case where the node keeps the name but changes the type */
bool const orig_alias = _alias;
_alias = xml.has_type("alias");
if (orig_alias != _alias)
_reset();
/* create alias or child depending of the node type */
if (_alias) {
if (!_alias_ptr)
_alias_ptr = &_factory.create_alias(_name);
} else {
if (!_child_ptr && _factory.ready_to_create_child(_name, _version))
_child_ptr = &_factory.create_child(xml);
}
/* update */
if (_alias_ptr)
_alias_ptr->update(xml);
if (_child_ptr)
_factory.update_child(*_child_ptr, xml);
}
void apply_child_restart(Xml_node const &xml)
{
if (_child_ptr && _child_ptr->restart_scheduled()) {
/* tear down */
_child_ptr->abandon();
_child_ptr = nullptr;
/* respawn */
update_from_xml(xml);
}
}
void trigger_start_child()
{
if (_child_ptr)
_child_ptr->try_start();
}
};
struct Sandbox::Service_model : Interface, Noncopyable
{
struct Factory : Interface, Noncopyable
{
virtual Service_model &create_service(Xml_node const &) = 0;
virtual void destroy_service(Service_model &) = 0;
};
virtual void update_from_xml(Xml_node const &) = 0;
virtual bool matches(Xml_node const &) = 0;
};
class Sandbox::Config_model : Noncopyable
{
private:
struct Node;
List_model<Node> _model { };
struct Parent_provides_node;
struct Default_route_node;
struct Default_node;
struct Affinity_space_node;
struct Start_node;
struct Report_node;
struct Resource_node;
struct Heartbeat_node;
struct Service_node;
public:
typedef State_reporter::Version Version;
void update_from_xml(Xml_node const &,
Allocator &,
Reconstructible<Verbose> &,
Version &,
Preservation &,
Constructible<Buffered_xml> &,
Cap_quota &,
Prio_levels &,
Constructible<Affinity::Space> &,
Start_model::Factory &,
Parent_provides_model::Factory &,
Service_model::Factory &,
State_reporter &,
Heartbeat &);
void apply_children_restart(Xml_node const &);
/*
* Call 'Child::try_start' for each child in start-node order
*/
void trigger_start_children();
};
#endif /* _CONFIG_MODEL_H_ */

View File

@ -64,23 +64,23 @@ class Sandbox::Heartbeat : Noncopyable
_timer_handler(_env.ep(), *this, &Heartbeat::_handle_timer)
{ }
void apply_config(Xml_node config)
void disable()
{
bool const enabled = config.has_sub_node("heartbeat");
_timer.conditional(enabled, _env);
if (!enabled) {
_timer.destruct();
_rate_ms = 0;
return;
}
unsigned const rate_ms =
config.sub_node("heartbeat").attribute_value("rate_ms", 1000UL);
void apply_config(Xml_node heartbeat)
{
if (!_timer.constructed()) {
_timer.construct(_env);
_timer->sigh(_timer_handler);
}
unsigned const rate_ms = heartbeat.attribute_value("rate_ms", 1000UL);
if (rate_ms != _rate_ms) {
_rate_ms = rate_ms;
_timer->sigh(_timer_handler);
_timer->trigger_periodic(_rate_ms*1000);
}
}

View File

@ -5,7 +5,7 @@
*/
/*
* Copyright (C) 2010-2020 Genode Labs GmbH
* Copyright (C) 2010-2021 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.
@ -20,12 +20,15 @@
#include <alias.h>
#include <server.h>
#include <heartbeat.h>
#include <config_model.h>
struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
::Sandbox::Child::Default_route_accessor,
::Sandbox::Child::Default_caps_accessor,
::Sandbox::Child::Ram_limit_accessor,
::Sandbox::Child::Cap_limit_accessor
::Sandbox::Child::Cap_limit_accessor,
::Sandbox::Start_model::Factory,
::Sandbox::Parent_provides_model::Factory
{
using Routed_service = ::Sandbox::Routed_service;
using Parent_service = ::Sandbox::Parent_service;
@ -41,6 +44,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
using Prio_levels = ::Sandbox::Prio_levels;
using Ram_info = ::Sandbox::Ram_info;
using Cap_info = ::Sandbox::Cap_info;
using Config_model = ::Sandbox::Config_model;
using Start_model = ::Sandbox::Start_model;
using Preservation = ::Sandbox::Preservation;
Env &_env;
Heap &_heap;
@ -50,77 +56,82 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
Registry<Local_service> &_local_services;
Child_registry _children { };
/*
* Global parameters obtained from config
*/
Reconstructible<Verbose> _verbose { };
Config_model::Version _version { };
Constructible<Buffered_xml> _default_route { };
Cap_quota _default_caps { 0 };
Prio_levels _prio_levels { };
Constructible<Affinity::Space> _affinity_space { };
Preservation _preservation { };
unsigned _child_cnt = 0;
static Ram_quota _preserved_ram_from_config(Xml_node config)
Affinity::Space _effective_affinity_space() const
{
Number_of_bytes preserve { 40*sizeof(long)*1024 };
config.for_each_sub_node("resource", [&] (Xml_node node) {
if (node.attribute_value("name", String<16>()) == "RAM")
preserve = node.attribute_value("preserve", preserve); });
return Ram_quota { preserve };
return _affinity_space.constructed() ? *_affinity_space
: Affinity::Space { 1, 1 };
}
Ram_quota _preserved_ram { 0 };
Cap_quota _preserved_caps { 0 };
State_reporter _state_reporter;
Heartbeat _heartbeat { _env, _children, _state_reporter };
/*
* Internal representation of the XML configuration
*/
Config_model _config_model { };
/*
* Variables for tracking the side effects of updating the config model
*/
bool _server_appeared_or_disappeared = false;
bool _state_report_outdated = false;
unsigned _child_cnt = 0;
Ram_quota _avail_ram() const
{
Ram_quota avail_ram = _env.pd().avail_ram();
if (_preserved_ram.value > avail_ram.value) {
if (_preservation.ram.value > avail_ram.value) {
error("RAM preservation exceeds available memory");
return Ram_quota { 0 };
}
/* deduce preserved quota from available quota */
return Ram_quota { avail_ram.value - _preserved_ram.value };
}
static Cap_quota _preserved_caps_from_config(Xml_node config)
{
size_t preserve = 20;
config.for_each_sub_node("resource", [&] (Xml_node node) {
if (node.attribute_value("name", String<16>()) == "CAP")
preserve = node.attribute_value("preserve", preserve); });
return Cap_quota { preserve };
return Ram_quota { avail_ram.value - _preservation.ram.value };
}
Cap_quota _avail_caps() const
{
Cap_quota avail_caps { _env.pd().avail_caps().value };
if (_preserved_caps.value > avail_caps.value) {
if (_preservation.caps.value > avail_caps.value) {
error("Capability preservation exceeds available capabilities");
return Cap_quota { 0 };
}
/* deduce preserved quota from available quota */
return Cap_quota { avail_caps.value - _preserved_caps.value };
return Cap_quota { avail_caps.value - _preservation.caps.value };
}
/**
* Child::Ram_limit_accessor interface
*/
Ram_quota resource_limit(Ram_quota const &) const override { return _avail_ram(); }
Ram_quota resource_limit(Ram_quota const &) const override
{
return _avail_ram();
}
/**
* Child::Cap_limit_accessor interface
*/
Cap_quota resource_limit(Cap_quota const &) const override { return _avail_caps(); }
void _handle_resource_avail() { }
/**
* State_reporter::Producer interface
*/
void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override
{
if (detail.init_ram())
@ -133,6 +144,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
_children.report_state(xml, detail);
}
/**
* State_reporter::Producer interface
*/
Child::Sample_state_result sample_children_state() override
{
return _children.sample_state();
@ -152,18 +166,57 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
*/
Cap_quota default_caps() override { return _default_caps; }
State_reporter _state_reporter;
Heartbeat _heartbeat { _env, _children, _state_reporter };
void _update_aliases_from_config(Xml_node const &);
void _update_parent_services_from_config(Xml_node const &);
void _abandon_obsolete_children(Xml_node const &);
void _update_children_config(Xml_node const &);
void _destroy_abandoned_parent_services();
void _destroy_abandoned_children();
Server _server { _env, _heap, _child_services, _state_reporter };
/**
* Sandbox::Start_model::Factory
*/
Child &create_child(Xml_node const &) override;
/**
* Sandbox::Start_model::Factory
*/
void update_child(Child &, Xml_node const &) override;
/**
* Sandbox::Start_model::Factory
*/
Alias &create_alias(Child_policy::Name const &name) override
{
Alias &alias = *new (_heap) Alias(name);
_children.insert_alias(&alias);
return alias;
}
/**
* Sandbox::Start_model::Factory
*/
void destroy_alias(Alias &alias) override
{
_children.remove_alias(&alias);
destroy(_heap, &alias);
}
/**
* Sandbox::Start_model::Factory
*/
bool ready_to_create_child(Start_model::Name const &,
Start_model::Version const &) const override;
/**
* Sandbox::Parent_provides_model::Factory
*/
Parent_service &create_parent_service(Service::Name const &name) override
{
return *new (_heap) Parent_service(_parent_services, _env, name);
}
Library(Env &env, Heap &heap, Registry<Local_service> &local_services,
State_handler &state_handler)
:
@ -180,52 +233,6 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer,
};
void Genode::Sandbox::Library::_update_parent_services_from_config(Xml_node const &config)
{
Xml_node const node = config.has_sub_node("parent-provides")
? config.sub_node("parent-provides")
: Xml_node("<empty/>");
/* remove services that are no longer present in config */
_parent_services.for_each([&] (Parent_service &service) {
Service::Name const name = service.name();
bool obsolete = true;
node.for_each_sub_node("service", [&] (Xml_node service) {
if (name == service.attribute_value("name", Service::Name())) {
obsolete = false; }});
if (obsolete)
service.abandon();
});
/* used to prepend the list of new parent services with title */
bool first_log = true;
/* register new services */
node.for_each_sub_node("service", [&] (Xml_node service) {
Service::Name const name = service.attribute_value("name", Service::Name());
bool registered = false;
_parent_services.for_each([&] (Parent_service const &service) {
if (service.name() == name)
registered = true; });
if (!registered) {
new (_heap) ::Sandbox::Parent_service(_parent_services, _env, name);
if (_verbose->enabled()) {
if (first_log)
log("parent provides");
log(" service \"", name, "\"");
first_log = false;
}
}
});
}
void Genode::Sandbox::Library::_destroy_abandoned_parent_services()
{
_parent_services.for_each([&] (Parent_service &service) {
@ -234,109 +241,8 @@ void Genode::Sandbox::Library::_destroy_abandoned_parent_services()
}
void Genode::Sandbox::Library::_update_aliases_from_config(Xml_node const &config)
void Genode::Sandbox::Library::_destroy_abandoned_children()
{
/* remove all known aliases */
while (_children.any_alias()) {
::Sandbox::Alias *alias = _children.any_alias();
_children.remove_alias(alias);
destroy(_heap, alias);
}
/* create aliases */
config.for_each_sub_node("alias", [&] (Xml_node alias_node) {
try {
_children.insert_alias(new (_heap) Alias(alias_node)); }
catch (Alias::Name_is_missing) {
warning("missing 'name' attribute in '<alias>' entry"); }
catch (Alias::Child_is_missing) {
warning("missing 'child' attribute in '<alias>' entry"); }
});
}
void Genode::Sandbox::Library::_abandon_obsolete_children(Xml_node const &config)
{
_children.for_each_child([&] (Child &child) {
bool obsolete = true;
config.for_each_sub_node("start", [&] (Xml_node node) {
if (child.has_name (node.attribute_value("name", Child_policy::Name()))
&& child.has_version(node.attribute_value("version", Child::Version())))
obsolete = false; });
if (obsolete)
child.abandon();
});
}
void Genode::Sandbox::Library::_update_children_config(Xml_node const &config)
{
for (;;) {
/*
* Children are abandoned if any of their client sessions can no longer
* be routed or result in a different route. As each child may be a
* service, an avalanche effect may occur. It stops if no update causes
* a potential side effect in one iteration over all chilren.
*/
bool side_effects = false;
config.for_each_sub_node("start", [&] (Xml_node node) {
Child_policy::Name const start_node_name =
node.attribute_value("name", Child_policy::Name());
_children.for_each_child([&] (Child &child) {
if (!child.abandoned() && child.name() == start_node_name) {
switch (child.apply_config(node)) {
case Child::NO_SIDE_EFFECTS: break;
case Child::MAY_HAVE_SIDE_EFFECTS: side_effects = true; break;
};
}
});
});
if (!side_effects)
break;
}
}
void Genode::Sandbox::Library::apply_config(Xml_node const &config)
{
bool update_state_report = false;
_preserved_ram = _preserved_ram_from_config(config);
_preserved_caps = _preserved_caps_from_config(config);
_verbose.construct(config);
_state_reporter.apply_config(config);
_heartbeat.apply_config(config);
/* determine default route for resolving service requests */
try {
_default_route.construct(_heap, config.sub_node("default-route")); }
catch (...) { }
_default_caps = Cap_quota { 0 };
try {
_default_caps = Cap_quota { config.sub_node("default")
.attribute_value("caps", 0UL) }; }
catch (...) { }
Prio_levels const prio_levels = ::Sandbox::prio_levels_from_xml(config);
Affinity::Space const affinity_space = ::Sandbox::affinity_space_from_xml(config);
bool const space_defined = config.has_sub_node("affinity-space");
_update_aliases_from_config(config);
_update_parent_services_from_config(config);
_abandon_obsolete_children(config);
_update_children_config(config);
/* kill abandoned children */
_children.for_each_child([&] (Child &child) {
if (!child.abandoned())
@ -345,7 +251,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
/* make the child's services unavailable */
child.destroy_services();
child.close_all_sessions();
update_state_report = true;
_state_report_outdated = true;
/* destroy child once all environment sessions are gone */
if (child.env_sessions_closed()) {
@ -353,30 +259,18 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
destroy(_heap, &child);
}
});
}
_destroy_abandoned_parent_services();
/* initial RAM and caps limit before starting new children */
Ram_quota const avail_ram = _avail_ram();
Cap_quota const avail_caps = _avail_caps();
/* variable used to track the RAM and caps taken by new started children */
Ram_quota used_ram { 0 };
Cap_quota used_caps { 0 };
/* create new children */
try {
config.for_each_sub_node("start", [&] (Xml_node start_node) {
bool Genode::Sandbox::Library::ready_to_create_child(Start_model::Name const &name,
Start_model::Version const &version) const
{
bool exists = false;
unsigned num_abandoned = 0;
typedef Child_policy::Name Name;
Name const child_name = start_node.attribute_value("name", Name());
_children.for_each_child([&] (Child const &child) {
if (child.name() == child_name) {
if (child.name() == name && child.has_version(version)) {
if (child.abandoned())
num_abandoned++;
else
@ -384,52 +278,40 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
}
});
/* skip start node if corresponding child already exists */
/* defer child creation if corresponding child already exists */
if (exists)
return;
return false;
/* prevent queuing up abandoned children with the same name */
if (num_abandoned > 1)
return;
return false;
if (used_ram.value > avail_ram.value) {
error("RAM exhausted while starting child: ", child_name);
return;
return true;
}
if (used_caps.value > avail_caps.value) {
error("capabilities exhausted while starting child: ", child_name);
return;
}
if (!space_defined && start_node.has_sub_node("affinity")) {
::Sandbox::Child &Genode::Sandbox::Library::create_child(Xml_node const &start_node)
{
if (!_affinity_space.constructed() && start_node.has_sub_node("affinity"))
warning("affinity-space configuration missing, "
"but affinity defined for child: ", child_name);
}
"but affinity defined for child ",
start_node.attribute_value("name", Child_policy::Name()));
try {
Child &child = *new (_heap)
Child(_env, _heap, *_verbose,
Child::Id { ++_child_cnt }, _state_reporter,
start_node, *this, *this, _children,
Ram_quota { avail_ram.value - used_ram.value },
Cap_quota { avail_caps.value - used_caps.value },
*this, *this, prio_levels, affinity_space,
*this, *this, _prio_levels, _effective_affinity_space(),
_parent_services, _child_services, _local_services);
_children.insert(&child);
update_state_report = true;
if (start_node.has_sub_node("provides"))
_server_appeared_or_disappeared = true;
/* account for the start XML node buffered in the child */
size_t const metadata_overhead = start_node.size() + sizeof(Child);
_state_report_outdated = true;
/* track used memory and RAM limit */
used_ram = Ram_quota { used_ram.value
+ child.ram_quota().value
+ metadata_overhead };
used_caps = Cap_quota { used_caps.value
+ child.cap_quota().value };
return child;
}
catch (Rom_connection::Rom_connection_failed) {
/*
@ -451,25 +333,100 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
"during child construction"); }
catch (Service_denied) {
warning("failed to create session during child construction"); }
});
throw ::Sandbox::Start_model::Factory::Creation_failed();
}
catch (Xml_node::Nonexistent_sub_node) { error("no children to start"); }
catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); }
catch (Child_registry::Alias_name_is_not_unique) { }
void Genode::Sandbox::Library::update_child(Child &child, Xml_node const &start)
{
if (child.abandoned())
return;
switch (child.apply_config(start)) {
case Child::NO_SIDE_EFFECTS: break;
case Child::PROVIDED_SERVICES_CHANGED:
_server_appeared_or_disappeared = true;
_state_report_outdated = true;
break;
};
}
void Genode::Sandbox::Library::apply_config(Xml_node const &config)
{
_server_appeared_or_disappeared = false;
_state_report_outdated = false;
_config_model.update_from_xml(config,
_heap,
_verbose,
_version,
_preservation,
_default_route,
_default_caps,
_prio_levels,
_affinity_space,
*this, *this, _server,
_state_reporter,
_heartbeat);
/*
* Initiate RAM sessions of all new children
* After importing the new configuration, servers may have disappeared
* (STATE_ABANDONED) or become new available.
*
* Re-evaluate the dependencies of the existing children.
*
* - Stuck children (STATE_STUCK) may become alive.
* - Children with broken dependencies may have become stuck.
* - Children with changed dependencies need a restart.
*
* Children are restarted if any of their client sessions can no longer be
* routed or result in a different route. As each child may be a service,
* an avalanche effect may occur. It stops if no child gets scheduled to be
* restarted in one iteration over all children.
*/
while (true) {
bool any_restart_scheduled = false;
_children.for_each_child([&] (Child &child) {
if (!child.abandoned())
child.initiate_env_pd_session(); });
if (child.abandoned())
return;
if (child.restart_scheduled()) {
any_restart_scheduled = true;
return;
}
if (_server_appeared_or_disappeared || child.uncertain_dependencies())
child.evaluate_dependencies();
if (child.restart_scheduled())
any_restart_scheduled = true;
});
/*
* Initiate remaining environment sessions of all new children
* Release resources captured by abandoned children before starting
* new children. The children must be started in the order of their
* start nodes for the assignment of slack RAM.
*/
_children.for_each_child([&] (Child &child) {
if (!child.abandoned())
child.initiate_env_sessions(); });
_destroy_abandoned_parent_services();
_destroy_abandoned_children();
_config_model.trigger_start_children();
if (any_restart_scheduled)
_config_model.apply_children_restart(config);
if (!any_restart_scheduled)
break;
}
_server.apply_updated_policy();
/*
* (Re-)distribute RAM and capability quota among the children, given their
@ -480,9 +437,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config)
_children.for_each_child([&] (Child &child) { child.apply_downgrade(); });
_children.for_each_child([&] (Child &child) { child.apply_upgrade(); });
_server.apply_config(config);
if (update_state_report)
if (_state_report_outdated)
_state_reporter.trigger_immediate_report_update();
}

View File

@ -21,19 +21,21 @@
/******************************
** Sandbox::Server::Service **
**********...*****************/
******************************/
struct Sandbox::Server::Service
struct Sandbox::Server::Service : Service_model
{
typedef Genode::Service::Name Name;
Name const _name;
Registry<Service>::Element _registry_element;
Buffered_xml _service_node;
typedef Genode::Service::Name Name;
Allocator &_alloc;
Registry<Routed_service> &_child_services;
Name const _name { _service_node.xml().attribute_value("name", Name()) };
Constructible<Buffered_xml> _service_node { };
/**
* Constructor
@ -45,11 +47,28 @@ struct Sandbox::Server::Service
Xml_node service_node,
Registry<Routed_service> &child_services)
:
_name(service_node.attribute_value("name", Name())),
_registry_element(services, *this),
_service_node(alloc, service_node),
_alloc(alloc),
_child_services(child_services)
{ }
/**
* Service_model interface
*/
void update_from_xml(Xml_node const &service_node) override
{
_service_node.construct(_alloc, service_node);
}
/**
* Service_model interface
*/
bool matches(Xml_node const &service_node) override
{
return _name == service_node.attribute_value("name", Name());
}
/**
* Determine route to child service for a given label according
* to the <service> node policy
@ -65,8 +84,11 @@ struct Sandbox::Server::Service
Sandbox::Server::Route
Sandbox::Server::Service::resolve_session_request(Session_label const &label)
{
if (!_service_node.constructed())
throw Service_denied();
try {
Session_policy policy(label, _service_node.xml());
Session_policy policy(label, _service_node->xml());
if (!policy.has_sub_node("child"))
throw Service_denied();
@ -365,13 +387,20 @@ void Sandbox::Server::_handle_session_requests()
}
void Sandbox::Server::apply_config(Xml_node config)
Sandbox::Service_model &Sandbox::Server::create_service(Xml_node const &node)
{
_services.for_each([&] (Service &service) { destroy(_alloc, &service); });
return *new (_alloc) Service(_services, _alloc, node, _child_services);
}
config.for_each_sub_node("service", [&] (Xml_node node) {
new (_alloc) Service(_services, _alloc, node, _child_services); });
void Sandbox::Server::destroy_service(Service_model &service)
{
destroy(_alloc, &service);
}
void Sandbox::Server::apply_updated_policy()
{
/*
* Construct mechanics for responding to our parent's session requests
* on demand.

View File

@ -19,15 +19,17 @@
#include <os/buffered_xml.h>
/* local includes */
#include "types.h"
#include "service.h"
#include "state_reporter.h"
#include <types.h>
#include <service.h>
#include <state_reporter.h>
#include <config_model.h>
namespace Sandbox { class Server; }
class Sandbox::Server : Session_state::Ready_callback,
Session_state::Closed_callback
Session_state::Closed_callback,
public Service_model::Factory
{
private:
@ -113,7 +115,17 @@ class Sandbox::Server : Session_state::Ready_callback,
_report_update_trigger(report_update_trigger)
{ }
void apply_config(Xml_node);
void apply_updated_policy();
/**
* Service_model::Factory
*/
Service_model &create_service(Xml_node const &) override;
/**
* Service_model::Factory
*/
void destroy_service(Service_model &) override;
};
#endif /* _LIB__SANDBOX__SERVER_H_ */

View File

@ -29,6 +29,8 @@ class Sandbox::State_reporter : public Report_update_trigger
{
public:
typedef String<64> Version;
struct Producer : Interface
{
virtual void produce_state_report(Xml_generator &xml,
@ -53,7 +55,6 @@ class Sandbox::State_reporter : public Report_update_trigger
uint64_t _report_period_ms = 0;
/* version string from config, to be reflected in the report */
typedef String<64> Version;
Version _version { };
Constructible<Timer::Connection> _timer { };
@ -111,22 +112,18 @@ class Sandbox::State_reporter : public Report_update_trigger
_producer.produce_state_report(xml, *_report_detail);
}
void apply_config(Xml_node config)
void apply_config(Version const &version, Xml_node const &report)
{
try {
Xml_node report = config.sub_node("report");
if (report.type() == "report") {
_report_detail.construct(report);
_report_delay_ms = report.attribute_value("delay_ms", 100UL);
}
catch (Xml_node::Nonexistent_sub_node) {
} else {
_report_detail.construct();
_report_delay_ms = 0;
}
bool trigger_update = false;
Version const version = config.attribute_value("version", Version());
if (version != _version) {
_version = version;
trigger_update = true;

View File

@ -14,9 +14,9 @@
#ifndef _LIB__SANDBOX__TYPES_H_
#define _LIB__SANDBOX__TYPES_H_
#include <util/string.h>
#include <util/list.h>
#include <session/session.h>
#include <util/xml_generator.h>
#include <pd_session/pd_session.h>
namespace Sandbox {
@ -72,6 +72,22 @@ namespace Sandbox {
.avail = pd.avail_caps() };
}
struct Preservation : private Noncopyable
{
static Ram_quota default_ram() { return { 40*sizeof(long)*1024 }; }
static Cap_quota default_caps() { return { 20 }; }
Ram_quota ram { };
Cap_quota caps { };
void reset()
{
ram = default_ram();
caps = default_caps();
}
Preservation() { reset(); }
};
}
#endif /* _LIB__SANDBOX__TYPES_H_ */

View File

@ -0,0 +1,69 @@
/*
* \brief Convenience wrapper around 'List_model'
* \author Norman Feske
* \date 2021-04-01
*/
/*
* Copyright (C) 2021 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 _UPDATE_LIST_MODEL_H_
#define _UPDATE_LIST_MODEL_H_
/* Genode includes */
#include <util/list_model.h>
/* local includes */
#include <types.h>
namespace Sandbox {
template <typename NODE, typename CREATE_FN, typename DESTROY_FN, typename UPDATE_FN>
static inline void update_list_model_from_xml(List_model<NODE> &,
Xml_node const &,
CREATE_FN const &,
DESTROY_FN const &,
UPDATE_FN const &);
}
template <typename NODE, typename CREATE_FN, typename DESTROY_FN, typename UPDATE_FN>
void Sandbox::update_list_model_from_xml(List_model<NODE> &model,
Xml_node const &xml,
CREATE_FN const &create,
DESTROY_FN const &destroy,
UPDATE_FN const &update)
{
struct Model_update_policy : List_model<NODE>::Update_policy
{
CREATE_FN const &_create_fn;
DESTROY_FN const &_destroy_fn;
UPDATE_FN const &_update_fn;
Model_update_policy(CREATE_FN const &create_fn,
DESTROY_FN const &destroy_fn,
UPDATE_FN const &update_fn)
:
_create_fn(create_fn), _destroy_fn(destroy_fn), _update_fn(update_fn)
{ }
void destroy_element(NODE &node) { _destroy_fn(node); }
NODE &create_element(Xml_node xml) { return _create_fn(xml); }
void update_element(NODE &node, Xml_node xml) { _update_fn(node, xml); }
static bool element_matches_xml_node(NODE const &node, Xml_node xml)
{
return node.matches(xml);
}
} policy(create, destroy, update);
model.update_from_xml(policy, xml);
}
#endif /* _UPDATE_LIST_MODEL_H_ */

View File

@ -124,6 +124,7 @@ namespace Sandbox {
/* count number of services with the specified name */
unsigned cnt = 0;
services.for_each([&] (T const &service) {
if (!service.abandoned())
cnt += (service.name() == name); });
return cnt > 1;
@ -164,7 +165,7 @@ namespace Sandbox {
/**
* Read priority-levels declaration from config
*/
inline Prio_levels prio_levels_from_xml(Xml_node config)
inline Prio_levels prio_levels_from_xml(Xml_node const &config)
{
long const prio_levels = config.attribute_value("prio_levels", 0UL);
@ -233,24 +234,6 @@ namespace Sandbox {
}
catch (...) { return Location(0, 0, space.width(), space.height()); }
}
/**
* Read affinity-space parameters from config
*
* If no affinity space is declared, construct a space with a single element,
* width and height being 1. If only one of both dimensions is specified, the
* other dimension is set to 1.
*/
inline Affinity::Space affinity_space_from_xml(Xml_node config)
{
try {
Xml_node node = config.sub_node("affinity-space");
return Affinity::Space(node.attribute_value<unsigned long>("width", 1),
node.attribute_value<unsigned long>("height", 1));
} catch (...) {
return Affinity::Space(1, 1); }
}
}
#endif /* _LIB__SANDBOX__UTILS_H_ */

View File

@ -34,7 +34,7 @@ class Sandbox::Verbose : Genode::Noncopyable
Verbose() { }
Verbose(Genode::Xml_node config)
Verbose(Xml_node const &config)
: _enabled(config.attribute_value("verbose", false)) { }
bool enabled() const { return _enabled; }