init: apply changes of <provides> nodes

This patch enables init to apply changes of any server's <provides>
declarations in a differential way. Servers can in principle be extended
by new services without re-starting them. Of course, changes of the
<provides> declarations may affect clients or would-be clients as this
information is taken into account for the session routing.
This commit is contained in:
Norman Feske 2017-03-03 12:50:29 +01:00 committed by Christian Helmuth
parent 0202048eb6
commit 9dca1503a8
3 changed files with 213 additions and 21 deletions

View File

@ -658,6 +658,49 @@ class Init::Child : Child_policy, Child_service::Wakeup
catch (Parent::Service_denied) { return false; }
}
static Xml_node _provides_sub_node(Xml_node start_node)
{
return start_node.has_sub_node("provides")
? start_node.sub_node("provides") : Xml_node("<provides/>");
}
/**
* Return true if service is provided by this child
*/
bool _provided_by_this(Routed_service const &service)
{
return service.has_id_space(_session_requester.id_space());
}
/**
* Return true if service of specified <provides> sub node is known
*/
bool _service_exists(Xml_node node) const
{
bool exists = false;
_child_services.for_each([&] (Routed_service const &service) {
if (_provided_by_this(service) &&
service.name() == node.attribute_value("name", Service::Name()))
exists = true; });
return exists;
}
void _add_service(Xml_node service)
{
Service::Name const name =
service.attribute_value("name", Service::Name());
if (_verbose.enabled())
log(" provides service ", name);
new (_alloc)
Routed_service(_child_services, this->name(), _ram_accessor,
_session_requester.id_space(),
_child.session_factory(),
name, *this);
}
public:
/**
@ -719,25 +762,9 @@ class Init::Child : Child_policy, Child_service::Wakeup
/*
* Determine services provided by the child
*/
try {
Xml_node service_node = start_node.sub_node("provides").sub_node("service");
for (; ; service_node = service_node.next("service")) {
char name[Service::Name::capacity()];
service_node.attribute("name").value(name, sizeof(name));
if (_verbose.enabled())
log(" provides service ", Cstring(name));
new (_alloc)
Routed_service(child_services, this->name(), _ram_accessor,
_session_requester.id_space(),
_child.session_factory(),
name, *this);
}
}
catch (Xml_node::Nonexistent_sub_node) { }
_provides_sub_node(start_node)
.for_each_sub_node("service",
[&] (Xml_node node) { _add_service(node); });
/*
* Construct inline config ROM service if "config" node is present.
@ -812,6 +839,17 @@ class Init::Child : Child_policy, Child_service::Wakeup
if (_state == STATE_ABANDONED)
return NO_SIDE_EFFECTS;
/*
* If the child's environment is incomplete, restart it to attempt
* the re-routing of its environment sessions.
*/
if (!_child.active()) {
abandon();
return MAY_HAVE_SIDE_EFFECTS;
}
bool provided_services_changed = false;
enum Config_update { CONFIG_APPEARED, CONFIG_VANISHED,
CONFIG_CHANGED, CONFIG_UNCHANGED };
@ -856,6 +894,41 @@ class Init::Child : Child_policy, Child_service::Wakeup
config_update = CONFIG_CHANGED;
}
/*
* Import updated <provides> node
*
* First abandon services that are no longer present in the
* <provides> node. Then add services that have newly appeared.
*/
_child_services.for_each([&] (Routed_service &service) {
if (!_provided_by_this(service))
return;
typedef Service::Name Name;
Name const name = service.name();
bool still_provided = false;
_provides_sub_node(start_node)
.for_each_sub_node("service", [&] (Xml_node node) {
if (name == node.attribute_value("name", Name()))
still_provided = true; });
if (!still_provided) {
service.abandon();
provided_services_changed = true;
}
});
_provides_sub_node(start_node).for_each_sub_node("service",
[&] (Xml_node node) {
if (_service_exists(node))
return;
_add_service(node);
provided_services_changed = true;
});
/*
* Import new binary name. A change may affect the route for
* the binary's ROM session, triggering the restart of the
@ -892,6 +965,10 @@ class Init::Child : Child_policy, Child_service::Wakeup
return MAY_HAVE_SIDE_EFFECTS;
}
}
if (provided_services_changed)
return MAY_HAVE_SIDE_EFFECTS;
return NO_SIDE_EFFECTS;
}
@ -908,6 +985,9 @@ class Init::Child : Child_policy, Child_service::Wakeup
if (detail.ids())
xml.attribute("id", _id.value);
if (!_child.active())
xml.attribute("state", "incomplete");
if (detail.child_ram() && _child.ram_session_cap().valid()) {
xml.node("ram", [&] () {
/*

View File

@ -200,6 +200,111 @@ append config {
<sleep ms="100"/>
<message string="test changing provided services"/>
<!-- Initially, the log service lacks the <provides> declaration.
Therefore the attempt to route the LOG session of the client
to the 'log' fails. The environment of 'dummy' will remain
incomplete. -->
<init_config>
<report requested="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="log">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config/>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="dummy">
<resource name="RAM" quantum="1M"/>
<config> <log string="started"/> </config>
<route>
<service name="LOG"> <child name="log"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<sleep ms="200"/>
<expect_init_state>
<node name="child"> <attribute name="name" value="log"/> </node>
<node name="child">
<attribute name="name" value="dummy"/>
<attribute name="state" value="incomplete"/>
</node>
</expect_init_state>
<!-- We add the <provides> node to the log server and thereby
make the LOG route of the 'dummy' client available. The
server is expected to remain unaffected but the client should
be restarted to re-route its LOG session to the server -->
<init_config>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="log">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config version="providing service"> <log_service/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="dummy">
<resource name="RAM" quantum="1M"/>
<config> <log string="started"/> </config>
<route>
<service name="LOG"> <child name="log"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<expect_log string="[init -> log] config 2: providing service"/>
<expect_log string="[init -> log] [dummy] started"/>
<!-- We remove <provides> node from 'log' service and thereby
make the LOG service unavailble for 'dummy'. Consequently,
'dummy' will be restarted but will ultimately remain
incomplete (as the LOG environment session cannot be routed).
The server stays alive and reports its third config. -->
<init_config>
<report requested="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="log">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config version="became unavailable"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="dummy">
<resource name="RAM" quantum="1M"/>
<config> <log string="started"/> </config>
<route>
<service name="LOG"> <child name="log"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<sleep ms="150"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="dummy"/>
<attribute name="state" value="incomplete"/>
</node>
</expect_init_state>
<message string="update child config"/>
<init_config>

View File

@ -178,6 +178,8 @@ struct Test::Main : Log_message_handler
Timer::Connection _timer { _env };
bool _timer_scheduled = false;
Reporter _init_config_reporter { _env, "config", "init.config" };
Attached_rom_dataspace _config { _env, "config" };
@ -269,8 +271,11 @@ struct Test::Main : Log_message_handler
}
if (step.type() == "sleep") {
unsigned long const timeout_ms = step.attribute_value("ms", 250UL);
_timer.trigger_once(timeout_ms*1000);
if (!_timer_scheduled) {
unsigned long const timeout_ms = step.attribute_value("ms", 250UL);
_timer.trigger_once(timeout_ms*1000);
_timer_scheduled = true;
}
return;
}
@ -311,6 +316,8 @@ struct Test::Main : Log_message_handler
throw Exception();
}
_timer_scheduled = false;
_advance_step();
_execute_curr_step();
}