init: dynamic configuration

This patch lets init apply configuration changes to a running scenario
in a differential way. Children are restarted if any of their session
routes change, new children can be added to a running scenario, or
children can deliberately be removed.

Furthermore, the new version of init is able to propagate configuration
changes (modifications of <config> nodes) to its children without
restarting them.
This commit is contained in:
Norman Feske 2017-02-21 23:07:20 +01:00 committed by Christian Helmuth
parent bc236bbcde
commit 150c286f0e
5 changed files with 711 additions and 88 deletions

View File

@ -22,12 +22,13 @@
/* init includes */
#include <init/verbose.h>
#include <init/child_config.h>
#include <init/child_policy.h>
#include <init/report.h>
namespace Init {
class Abandonable;
class Parent_service;
class Buffered_xml;
class Routed_service;
class Name_registry;
@ -37,8 +38,6 @@ namespace Init {
using namespace Genode;
using Genode::size_t;
using Genode::strlen;
typedef Genode::Registered<Genode::Parent_service> Parent_service;
}
@ -226,8 +225,8 @@ namespace Init {
template <typename T>
inline Service *find_service(Registry<T> &services,
Service::Name const &name)
inline T *find_service(Registry<T> &services,
Service::Name const &name)
{
T *service = nullptr;
services.for_each([&] (T &s) {
@ -253,6 +252,36 @@ namespace Init {
}
class Init::Abandonable
{
private:
bool _abandoned = false;
public:
void abandon() { _abandoned = true; }
bool abandoned() const { return _abandoned; }
};
class Init::Parent_service : public Genode::Parent_service, public Abandonable
{
private:
Registry<Parent_service>::Element _reg_elem;
public:
Parent_service(Registry<Parent_service> &registry, Env &env,
Service::Name const &name)
:
Genode::Parent_service(env, name), _reg_elem(registry, *this)
{ }
};
class Init::Buffered_xml
{
private:
@ -292,16 +321,20 @@ class Init::Buffered_xml
/**
* Init-specific representation of a child service
*/
class Init::Routed_service : public Child_service
class Init::Routed_service : public Child_service, public Abandonable
{
public:
typedef Child_policy::Name Child_name;
struct Ram_accessor { virtual Ram_session_capability ram() const = 0; };
private:
Child_name _child_name;
Ram_accessor &_ram_accessor;
Registry<Routed_service>::Element _registry_element;
public:
@ -316,17 +349,21 @@ class Init::Routed_service : public Child_service
*/
Routed_service(Registry<Routed_service> &services,
Child_name const &child_name,
Ram_accessor &ram_accessor,
Id_space<Parent::Server> &server_id_space,
Session_state::Factory &factory,
Service::Name const &name,
Ram_session_capability ram,
Child_service::Wakeup &wakeup)
:
Child_service(server_id_space, factory, name, ram, wakeup),
_child_name(child_name), _registry_element(services, *this)
Child_service(server_id_space, factory, name,
Ram_session_capability(), wakeup),
_child_name(child_name), _ram_accessor(ram_accessor),
_registry_element(services, *this)
{ }
Child_name const &child_name() const { return _child_name; }
Ram_session_capability ram() const { return _ram_accessor.ram(); }
};
@ -387,6 +424,11 @@ class Init::Child : Child_policy, Child_service::Wakeup
Id const _id;
enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE,
STATE_ABANDONED };
State _state = STATE_INITIAL;
Report_update_trigger &_report_update_trigger;
List_element<Child> _list_element;
@ -525,10 +567,52 @@ class Init::Child : Child_policy, Child_service::Wakeup
Registry<Parent_service> &_parent_services;
Registry<Routed_service> &_child_services;
/**
* Private child configuration
*/
Init::Child_config _config;
struct Inline_config_rom_service : Abandonable, Dynamic_rom_session::Content_producer
{
typedef Local_service<Dynamic_rom_session> Service;
Child &_child;
Dynamic_rom_session _session { _child._env.ep().rpc_ep(),
_child.ref_ram(), _child._env.rm(),
*this };
Service::Single_session_factory _factory { _session };
Service _service { _factory };
Inline_config_rom_service(Child &child) : _child(child) { }
/**
* Dynamic_rom_session::Content_producer interface
*/
void produce_content(char *dst, Genode::size_t dst_len) override
{
Xml_node config = _child._start_node->xml().has_sub_node("config")
? _child._start_node->xml().sub_node("config")
: Xml_node("<config/>");
size_t const config_len = config.size();
if (config_len + 1 /* null termination */ >= dst_len)
throw Buffer_capacity_exceeded();
/*
* The 'config.size()' method returns the number of bytes of
* the config-node content, which is not null-terminated. Since
* 'Genode::strncpy' always null-terminates the result, the
* last byte of the source string is not copied. Hence, it is
* safe to add '1' to 'config_len' and thereby include the
* last actual config-content character in the result.
*/
Genode::strncpy(dst, config.addr(), config_len + 1);
}
void trigger_update() { _session.trigger_update(); }
Service &service() { return _service; }
};
Constructible<Inline_config_rom_service> _config_rom_service;
Session_requester _session_requester;
@ -536,12 +620,18 @@ class Init::Child : Child_policy, Child_service::Wakeup
* Policy helpers
*/
Init::Child_policy_handle_cpu_priorities _priority_policy;
Init::Child_policy_provide_rom_file _config_policy;
Init::Child_policy_redirect_rom_file _configfile_policy;
Init::Child_policy_ram_phys _ram_session_policy;
Genode::Child _child { _env.rm(), _env.ep().rpc_ep(), *this };
struct Ram_accessor : Routed_service::Ram_accessor
{
Genode::Child &_child;
Ram_accessor(Genode::Child &child) : _child(child) { }
Ram_session_capability ram() const override {
return _child.ram_session_cap(); }
} _ram_accessor { _child };
/**
* Child_service::Wakeup callback
*/
@ -550,6 +640,26 @@ class Init::Child : Child_policy, Child_service::Wakeup
_session_requester.trigger_update();
}
/**
* Return true if the policy results in the current route of the session
*
* This method is used to check if a policy change affects an existing
* client session of a child, i.e., to determine whether the child must
* be restarted.
*/
bool _route_valid(Session_state const &session)
{
try {
Route const route =
resolve_session_request(session.service().name(),
session.client_label());
return (session.service() == route.service)
&& (route.label == session.label());
}
catch (Parent::Service_denied) { return false; }
}
public:
/**
@ -591,11 +701,8 @@ class Init::Child : Child_policy, Child_service::Wakeup
avail_slack_ram_quota(_env.ram().avail()), _verbose),
_parent_services(parent_services),
_child_services(child_services),
_config(_env.ram(), _env.rm(), start_node),
_session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm()),
_priority_policy(_resources.prio_levels_log2, _resources.priority),
_config_policy("config", _config.dataspace(), &_env.ep().rpc_ep()),
_configfile_policy("config", _config.filename()),
_ram_session_policy(_resources.constrain_phys)
{
if (_resources.ram_quota == 0)
@ -624,13 +731,19 @@ class Init::Child : Child_policy, Child_service::Wakeup
log(" provides service ", Cstring(name));
new (_alloc)
Routed_service(child_services, this->name(),
Routed_service(child_services, this->name(), _ram_accessor,
_session_requester.id_space(),
_child.session_factory(),
name, _child.ram_session_cap(), *this);
name, *this);
}
}
catch (Xml_node::Nonexistent_sub_node) { }
/*
* Construct inline config ROM service if "config" node is present.
*/
if (start_node.has_sub_node("config"))
_config_rom_service.construct(*this);
}
virtual ~Child()
@ -645,6 +758,125 @@ class Init::Child : Child_policy, Child_service::Wakeup
*/
bool has_name(Child_policy::Name const &str) const { return str == name(); }
void initiate_env_ram_session()
{
if (_state == STATE_INITIAL) {
_child.initiate_env_ram_session();
_state = STATE_RAM_INITIALIZED;
}
}
void initiate_env_sessions()
{
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.phase != Session_state::AVAILABLE)
warning(name(), ": incomplete environment ",
session.service().name(), " session "
"(", session.label(), ")"); });
_state = STATE_ALIVE;
}
}
void abandon()
{
_state = STATE_ABANDONED;
_child_services.for_each([&] (Routed_service &service) {
if (service.has_id_space(_session_requester.id_space()))
service.abandon(); });
}
bool abandoned() const { return _state == STATE_ABANDONED; }
enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS };
/**
* Apply new configuration to child
*
* \throw Allocator::Out_of_memory unable to allocate buffer for new
* config
*/
Apply_config_result apply_config(Xml_node start_node)
{
Child_policy &policy = *this;
if (_state == STATE_ABANDONED)
return NO_SIDE_EFFECTS;
enum Config_update { CONFIG_APPEARED, CONFIG_VANISHED,
CONFIG_CHANGED, CONFIG_UNCHANGED };
Config_update config_update = CONFIG_UNCHANGED;
/* import new start node if new version differs */
if (start_node.size() != _start_node->xml().size() ||
Genode::memcmp(start_node.addr(), _start_node->xml().addr(),
start_node.size()) != 0)
{
/*
* Start node changed
*
* 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)
config_update = CONFIG_VANISHED;
if (!config_was_present && config_is_present)
config_update = CONFIG_APPEARED;
if (config_was_present && config_is_present) {
Xml_node old_config = _start_node->xml().sub_node(tag);
Xml_node new_config = start_node.sub_node(tag);
if (Genode::memcmp(old_config.addr(), new_config.addr(),
min(old_config.size(), new_config.size())))
config_update = CONFIG_CHANGED;
}
/* import new start node */
_start_node.construct(_alloc, 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.
*/
switch (config_update) {
case CONFIG_UNCHANGED: break;
case CONFIG_CHANGED: _config_rom_service->trigger_update(); break;
case CONFIG_APPEARED: _config_rom_service.construct(*this); break;
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;
}
}
return NO_SIDE_EFFECTS;
}
void report_state(Xml_generator &xml, Report_detail const &detail) const
{
xml.node("child", [&] () {
@ -731,11 +963,41 @@ class Init::Child : Child_policy, Child_service::Wakeup
Route resolve_session_request(Service::Name const &service_name,
Session_label const &label) override
{
Service *service = nullptr;
/* check for "config" ROM request */
if ((service = _config_policy.resolve_session_request_with_label(service_name, label)))
return Route { *service, label };
if (service_name == Rom_session::service_name() &&
label.last_element() == "config") {
if (_config_rom_service.constructed() &&
!_config_rom_service->abandoned())
return Route { _config_rom_service->service(), label };
/*
* \deprecated the support for the <configfile> tag will
* be removed
*/
if (_start_node->xml().has_sub_node("configfile")) {
typedef String<50> Name;
Name const rom =
_start_node->xml().sub_node("configfile")
.attribute_value("name", Name());
/* prevent infinite recursion */
if (rom == "config") {
error("configfile must not be named 'config'");
throw Parent::Service_denied();
}
return resolve_session_request(service_name,
prefixed_label(name(), rom));
}
/*
* If there is neither an inline '<config>' nor a
* '<configfile>' node present, we apply the regular session
* routing to the "config" ROM request.
*/
}
/* check for "session_requests" ROM request */
if (service_name == Rom_session::service_name()
@ -774,9 +1036,14 @@ class Init::Child : Child_policy, Child_service::Wakeup
if (target.has_type("parent")) {
Parent_service *service = nullptr;
if ((service = find_service(_parent_services, service_name)))
return Route { *service, target_label };
if (service && service->abandoned())
throw Parent::Service_denied();
if (!service_wildcard) {
warning(name(), ": service lookup for "
"\"", service_name, "\" at parent failed");
@ -790,10 +1057,16 @@ class Init::Child : Child_policy, Child_service::Wakeup
Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name);
Routed_service *service = nullptr;
_child_services.for_each([&] (Routed_service &s) {
if (s.name() == Service::Name(service_name)
&& s.child_name() == server_name)
service = &s; });
if (service && service->abandoned())
throw Parent::Service_denied();
if (service)
return Route { *service, target_label };
@ -805,12 +1078,15 @@ class Init::Child : Child_policy, Child_service::Wakeup
}
if (target.has_type("any-child")) {
if (is_ambiguous(_child_services, service_name)) {
error(name(), ": ambiguous routes to "
"service \"", service_name, "\"");
throw Parent::Service_denied();
}
Routed_service *service = nullptr;
if ((service = find_service(_child_services, service_name)))
return Route { *service, target_label };
@ -825,21 +1101,16 @@ class Init::Child : Child_policy, Child_service::Wakeup
break;
}
}
} catch (...) {
warning(name(), ": no route to service \"", service_name, "\"");
}
} catch (Xml_node::Nonexistent_sub_node) { }
if (!service)
throw Parent::Service_denied();
return Route { *service };
warning(name(), ": no route to service \"", service_name, "\"");
throw Parent::Service_denied();
}
void filter_session_args(Service::Name const &service,
char *args, size_t args_len) override
{
_priority_policy. filter_session_args(service.string(), args, args_len);
_configfile_policy. filter_session_args(service.string(), args, args_len);
_ram_session_policy.filter_session_args(service.string(), args, args_len);
}
@ -923,6 +1194,8 @@ class Init::Child : Child_policy, Child_service::Wakeup
{
_report_update_trigger.trigger_report_update();
}
bool initiate_env_sessions() const override { return false; }
};
#endif /* _INCLUDE__INIT__CHILD_H_ */

View File

@ -14,6 +14,8 @@
#ifndef _INCLUDE__INIT__CHILD_CONFIG_H_
#define _INCLUDE__INIT__CHILD_CONFIG_H_
#warning header is deprecated, used os/dynamic_rom_session.h instead
#include <util/xml_node.h>
#include <base/attached_dataspace.h>
#include <ram_session/ram_session.h>

View File

@ -49,7 +49,7 @@ append config {
<config>
<!-- let init settle, processing the initially invalid config -->
<sleep ms="250"/>
<sleep ms="150"/>
<message string="test state reporting"/>
@ -89,6 +89,151 @@ append config {
</node>
</expect_init_state>
<message string="routing to custom log service"/>
<init_config version="chained log services">
<report ids="yes" requested="yes" provided="yes"/>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="indirect_server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service/> </config>
<route>
<service name="LOG"> <child name="server"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="client">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config> <log string="client started"/> </config>
<route>
<service name="LOG"> <child name="indirect_server"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<expect_log string="[init -> server] [indirect_server] [client] client started"/>
<sleep ms="200"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="server"/>
<attribute name="id" value="2"/>
</node>
<node name="child">
<attribute name="name" value="client"/>
<attribute name="id" value="4"/>
</node>
</expect_init_state>
<sleep ms="150"/>
<message string="changing route of indirect server"/>
<!-- Because the route to the LOG session of the 'indirect_server'
is re-directed to the parent, the 'indirect_server' must be
restarted. As the 'client' depends on the 'indirect_server'
for its LOG session, the client must implicitly be restarted
as well. -->
<init_config version="restarted indirect server">
<report ids="yes" requested="yes" provided="yes"/>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="CPU"/>
<service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="indirect_server">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<provides> <service name="LOG"/> </provides>
<config> <log_service/> </config>
<route> <any-service> <parent/> </any-service> </route>
</start>
<start name="client">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config> <log string="client started"/> </config>
<route>
<service name="LOG"> <child name="indirect_server"/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
</init_config>
<!-- the output of the new 'client' does no longer go via 'server' -->
<expect_log string="[init -> indirect_server] [client] client started"/>
<sleep ms="150"/>
<expect_init_state>
<node name="child">
<attribute name="name" value="server"/>
<attribute name="id" value="2"/>
</node>
<node name="child">
<attribute name="name" value="client"/>
<attribute name="id" value="6"/> <!-- client was restarted -->
</node>
</expect_init_state>
<sleep ms="100"/>
<message string="update child config"/>
<init_config>
<report ids="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="application">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config version="Version A"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> application] config 1: Version A"/>
<init_config>
<report ids="yes"/>
<parent-provides>
<service name="ROM"/> <service name="RAM"/>
<service name="CPU"/> <service name="PD"/>
<service name="LOG"/>
</parent-provides>
<start name="application">
<binary name="dummy"/>
<resource name="RAM" quantum="1M"/>
<config version="Version B"/>
<route> <any-service> <parent/> </any-service> </route>
</start>
</init_config>
<expect_log string="[init -> application] config 2: Version B"/>
<sleep ms="100"/>
</config>
<route>
<service name="Report"> <child name="report_rom"/> </service>

View File

@ -16,17 +16,68 @@
#include <base/registry.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <root/component.h>
#include <timer_session/connection.h>
#include <log_session/connection.h>
namespace Dummy {
struct Log_service;
struct Log_connections;
struct Main;
using namespace Genode;
}
struct Dummy::Log_service
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
struct Session_component : Rpc_object<Log_session>
{
Session_label const _label;
Session_component(Session_label const &label) : _label(label) { }
size_t write(String const &string) override
{
/* strip known line delimiter from incoming message */
unsigned n = 0;
Genode::String<16> const pattern("\033[0m\n");
for (char const *s = string.string(); s[n] && pattern != s + n; n++);
typedef Genode::String<100> Message;
Message const message("[", _label, "] ", Cstring(string.string(), n));
log(message);
return strlen(string.string());
}
};
struct Root : Root_component<Session_component>
{
Root(Entrypoint &ep, Allocator &alloc) : Root_component(ep, alloc) { }
Session_component *_create_session(const char *args, Affinity const &) override
{
return new (md_alloc()) Session_component(label_from_args(args));
}
};
Root _root { _env.ep(), _heap };
Log_service(Env &env) : _env(env)
{
_env.parent().announce(_env.ep().manage(_root));
log("created LOG service");
}
~Log_service() { _env.ep().dissolve(_root); }
};
struct Dummy::Log_connections
{
Env &_env;
@ -66,10 +117,28 @@ struct Dummy::Main
Attached_rom_dataspace _config { _env, "config" };
Constructible<Log_connections> _log_connections;
unsigned _config_cnt = 0;
Main(Env &env) : _env(env)
typedef String<50> Version;
Version _config_version;
Signal_handler<Main> _config_handler { _env.ep(), *this, &Main::_handle_config };
void _handle_config()
{
_config.update();
Version const version = _config.xml().attribute_value("version", Version());
if (_config_cnt > 0 && version == _config_version)
return;
_config_cnt++;
_config_version = version;
if (_config_version.valid())
log("config ", _config_cnt, ": ", _config_version);
_config.xml().for_each_sub_node([&] (Xml_node node) {
if (node.type() == "create_log_connections")
@ -78,6 +147,9 @@ struct Dummy::Main
if (node.type() == "destroy_log_connections")
_log_connections.destruct();
if (node.type() == "log_service")
_log_service.construct(_env);
if (node.type() == "sleep") {
if (!_timer.constructed())
@ -90,9 +162,17 @@ struct Dummy::Main
log(node.attribute_value("string", String<50>()));
});
}
Constructible<Log_connections> _log_connections;
Constructible<Log_service> _log_service;
Main(Env &env) : _env(env)
{
_config.sigh(_config_handler);
_handle_config();
}
};
void Component::construct(Genode::Env &env) { static Dummy::Main main(env); }

View File

@ -55,28 +55,6 @@ namespace Init {
} catch (...) {
return Affinity::Space(1, 1); }
}
/**
* Read parent-provided services from config
*/
inline void determine_parent_services(Registry<Init::Parent_service> &services,
Xml_node config, Allocator &alloc,
bool verbose)
{
if (verbose)
log("parent provides");
config.sub_node("parent-provides")
.for_each_sub_node("service", [&] (Xml_node node) {
Service::Name name = node.attribute_value("name", Service::Name());
new (alloc) Init::Parent_service(services, name);
if (verbose)
log(" service \"", name, "\"");
});
}
}
@ -213,11 +191,27 @@ class Init::Child_registry : public Name_registry, Child_list
return _aliases.first() ? _aliases.first() : 0;
}
void report_state(Xml_generator &xml, Report_detail const &detail) const
template <typename FN>
void for_each_child(FN const &fn) const
{
Genode::List_element<Child> const *curr = first();
for (; curr; curr = curr->next())
curr->object()->report_state(xml, detail);
fn(*curr->object());
}
template <typename FN>
void for_each_child(FN const &fn)
{
Genode::List_element<Child> *curr = first(), *next = nullptr;
for (; curr; curr = next) {
next = curr->next();
fn(*curr->object());
}
}
void report_state(Xml_generator &xml, Report_detail const &detail) const
{
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()) {
@ -226,7 +220,6 @@ class Init::Child_registry : public Name_registry, Child_list
xml.attribute("child", a->child);
});
}
}
@ -427,6 +420,11 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor
Signal_handler<Main> _resource_avail_handler {
_env.ep(), *this, &Main::_handle_resource_avail };
void _update_aliases_from_config();
void _update_parent_services_from_config();
void _abandon_obsolete_children();
void _update_children_config();
void _destroy_abandoned_parent_services();
void _handle_config();
Signal_handler<Main> _config_handler {
@ -444,15 +442,58 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor
};
void Init::Main::_handle_config()
void Init::Main::_update_parent_services_from_config()
{
/* kill all currently running children */
while (_children.any()) {
Init::Child *child = _children.any();
_children.remove(child);
destroy(_heap, child);
}
Xml_node const node = _config.xml().has_sub_node("parent-provides")
? _config.xml().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();
});
if (_verbose->enabled())
log("parent provides");
/* 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) Init::Parent_service(_parent_services, _env, name);
if (_verbose->enabled())
log(" service \"", name, "\"");
}
});
}
void Init::Main::_destroy_abandoned_parent_services()
{
_parent_services.for_each([&] (Parent_service &service) {
if (service.abandoned())
destroy(_heap, &service); });
}
void Init::Main::_update_aliases_from_config()
{
/* remove all known aliases */
while (_children.any_alias()) {
Init::Alias *alias = _children.any_alias();
@ -460,24 +501,6 @@ void Init::Main::_handle_config()
destroy(_heap, alias);
}
/* reset knowledge about parent services */
_parent_services.for_each([&] (Init::Parent_service &service) {
destroy(_heap, &service); });
_config.update();
_verbose.construct(_config.xml());
_state_reporter.apply_config(_config.xml());
try { determine_parent_services(_parent_services, _config.xml(),
_heap, _verbose->enabled()); }
catch (...) { }
/* determine default route for resolving service requests */
try {
_default_route.construct(_heap, _config.xml().sub_node("default-route")); }
catch (...) { }
/* create aliases */
_config.xml().for_each_sub_node("alias", [&] (Xml_node alias_node) {
@ -488,11 +511,99 @@ void Init::Main::_handle_config()
catch (Alias::Child_is_missing) {
warning("missing 'child' attribute in '<alias>' entry"); }
});
}
/* create children */
void Init::Main::_abandon_obsolete_children()
{
_children.for_each_child([&] (Child &child) {
Child_policy::Name const name = child.name();
bool obsolete = true;
_config.xml().for_each_sub_node("start", [&] (Xml_node node) {
if (node.attribute_value("name", Child_policy::Name()) == name)
obsolete = false; });
if (obsolete)
child.abandon();
});
}
void Init::Main::_update_children_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.xml().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.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 Init::Main::_handle_config()
{
_config.update();
_verbose.construct(_config.xml());
_state_reporter.apply_config(_config.xml());
/* determine default route for resolving service requests */
try {
_default_route.construct(_heap, _config.xml().sub_node("default-route")); }
catch (...) { }
_update_aliases_from_config();
_update_parent_services_from_config();
_abandon_obsolete_children();
_update_children_config();
/* kill abandoned children */
_children.for_each_child([&] (Child &child) {
if (child.abandoned()) {
_children.remove(&child);
destroy(_heap, &child);
}
});
_destroy_abandoned_parent_services();
/* create new children */
try {
_config.xml().for_each_sub_node("start", [&] (Xml_node start_node) {
/* skip start node if corresponding child already exists */
bool exists = false;
_children.for_each_child([&] (Child const &child) {
if (child.name() == start_node.attribute_value("name", Child_policy::Name()))
exists = true; });
if (exists) {
return;
}
try {
_children.insert(new (_heap)
Init::Child(_env, _heap, *_verbose,
@ -526,6 +637,18 @@ void Init::Main::_handle_config()
catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); }
catch (Init::Child::Child_name_is_not_unique) { }
catch (Init::Child_registry::Alias_name_is_not_unique) { }
/*
* Initiate RAM sessions of all new children
*/
_children.for_each_child([&] (Child &child) {
child.initiate_env_ram_session(); });
/*
* Initiate remaining environment sessions of all new children
*/
_children.for_each_child([&] (Child &child) {
child.initiate_env_sessions(); });
}