base: introduce Env::try_session

The new 'Env::try_session' method mirrors the existing 'Env::session'
without implicitly handling exceptions of the types 'Out_of_ram',
'Out_of_caps', 'Insufficient_ram_quota', and 'Insufficient_cap_quota'.
It enables runtime environments like init to reflect those exceptions to
their children instead of paying the costs of implicit session-quota
upgrades out of the own pocket.

By changing the 'Parent_service' to use 'try_session', this patch fixes
a resource-exhaustion problem of init in Sculpt OS that occurred when
the GPU multiplexer created a large batch of IO_MEM sessions, with each
session requiring a second attempt with the session quota upgraded by
4 KiB.

Issue #3767
This commit is contained in:
Norman Feske 2021-10-08 15:15:41 +02:00
parent d5d7915b4d
commit 6f1d3862cd
7 changed files with 153 additions and 41 deletions

View File

@ -68,6 +68,16 @@ struct Genode::Env : Interface
*/
virtual Id_space<Parent::Client> &id_space() = 0;
/**
* Create session with quota upgrades as needed
*
* \throw Service_denied
*
* In contrast to 'try_session', this method implicitly handles
* 'Insufficient_ram_quota' and 'Insufficient_cap_quota' by successively
* increasing the session quota. On the occurrence of an 'Out_of_ram'
* or 'Out_of_caps' exception, a resource request is issued to the parent.
*/
virtual Session_capability session(Parent::Service_name const &,
Parent::Client::Id,
Parent::Session_args const &,
@ -82,10 +92,6 @@ struct Genode::Env : Interface
* \param affinity preferred CPU affinity for the session
*
* \throw Service_denied
* \throw Insufficient_cap_quota
* \throw Insufficient_ram_quota
* \throw Out_of_caps
* \throw Out_of_ram
*
* See the documentation of 'Parent::session'.
*
@ -161,6 +167,20 @@ struct Genode::Env : Interface
* \noapi
*/
virtual void reinit_main_thread(Capability<Region_map> &stack_area_rm) = 0;
/**
* Attempt the creation of a session
*
* \throw Service_denied
* \throw Insufficient_cap_quota
* \throw Insufficient_ram_quota
* \throw Out_of_caps
* \throw Out_of_ram
*/
virtual Session_capability try_session(Parent::Service_name const &,
Parent::Client::Id,
Parent::Session_args const &,
Affinity const &) = 0;
};
#endif /* _INCLUDE__BASE__ENV_H_ */

View File

@ -27,6 +27,7 @@ namespace Genode {
class Service;
template <typename> class Session_factory;
template <typename> class Local_service;
class Try_parent_service;
class Parent_service;
class Async_service;
class Child_service;
@ -230,9 +231,14 @@ class Genode::Local_service : public Service
/**
* Representation of a service provided by our parent
* Representation of a strictly accounted service provided by our parent
*
* The 'Try_parent_service' reflects the local depletion of RAM or cap quotas
* during 'initiate_request' via 'Out_of_ram' or 'Out_of_caps' exceptions.
* This is appropriate in situations that demand strict accounting of resource
* use per child, e.g., child components hosted by the init component.
*/
class Genode::Parent_service : public Service
class Genode::Try_parent_service : public Service
{
private:
@ -240,12 +246,13 @@ class Genode::Parent_service : public Service
public:
/**
* Constructor
*/
Parent_service(Env &env, Service::Name const &name)
Try_parent_service(Env &env, Service::Name const &name)
: Service(name), _env(env) { }
/*
* \throw Out_of_ram
* \throw Out_of_caps
*/
void initiate_request(Session_state &session) override
{
switch (session.phase) {
@ -256,32 +263,35 @@ class Genode::Parent_service : public Service
_env.id_space());
try {
session.cap = _env.session(name().string(),
session.id_at_parent->id(),
Session_state::Server_args(session).string(),
session.affinity());
session.cap = _env.try_session(name().string(),
session.id_at_parent->id(),
Session_state::Server_args(session).string(),
session.affinity());
session.phase = Session_state::AVAILABLE;
}
catch (Out_of_ram) {
session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; }
session.phase = Session_state::CLOSED;
throw;
}
catch (Out_of_caps) {
session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; }
session.phase = Session_state::CLOSED;
throw;
}
catch (Insufficient_ram_quota) {
session.id_at_parent.destruct();
session.phase = Session_state::INSUFFICIENT_RAM_QUOTA; }
session.phase = Session_state::INSUFFICIENT_RAM_QUOTA;
}
catch (Insufficient_cap_quota) {
session.id_at_parent.destruct();
session.phase = Session_state::INSUFFICIENT_CAP_QUOTA; }
session.phase = Session_state::INSUFFICIENT_CAP_QUOTA;
}
catch (Service_denied) {
session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; }
session.phase = Session_state::SERVICE_DENIED;
}
break;
@ -326,6 +336,50 @@ class Genode::Parent_service : public Service
};
/**
* Representation of a service provided by our parent
*
* In contrast to 'Try_parent_service', the 'Parent_service' handles the
* exhaution of the local RAM or cap quotas by issuing resource requests.
* This is useful in situations where the parent is unconditionally willing
* to satisfy the resource needs of its children.
*/
class Genode::Parent_service : public Try_parent_service
{
private:
Env &_env;
public:
Parent_service(Env &env, Service::Name const &name)
: Try_parent_service(env, name), _env(env) { }
void initiate_request(Session_state &session) override
{
for (unsigned i = 0; i < 10; i++) {
try {
Try_parent_service::initiate_request(session);
return;
}
catch (Out_of_ram) {
Ram_quota ram_quota { ram_quota_from_args(session.args().string()) };
Parent::Resource_args args(String<64>("ram_quota=", ram_quota));
_env.parent().resource_request(args);
}
catch (Out_of_caps) {
Cap_quota cap_quota { cap_quota_from_args(session.args().string()) };
Parent::Resource_args args(String<64>("cap_quota=", cap_quota));
_env.parent().resource_request(args);
}
}
error("parent-session request repeatedly failed");
}
};
/**
* Representation of a service that asynchronously responds to session request
*/

View File

@ -116,10 +116,10 @@ namespace {
_session_blockade->block();
}
Session_capability session(Parent::Service_name const &name,
Parent::Client::Id id,
Parent::Session_args const &args,
Affinity const &affinity) override
Session_capability try_session(Parent::Service_name const &name,
Parent::Client::Id id,
Parent::Session_args const &args,
Affinity const &affinity) override
{
if (!args.valid_string()) {
warning(name.string(), " session denied because of truncated arguments");
@ -128,6 +128,20 @@ namespace {
Mutex::Guard guard(_mutex);
Session_capability cap = _parent.session(id, name, args, affinity);
if (cap.valid())
return cap;
_block_for_session();
return _parent.session_cap(id);
}
Session_capability session(Parent::Service_name const &name,
Parent::Client::Id id,
Parent::Session_args const &args,
Affinity const &affinity) override
{
/*
* Since we account for the backing store for session meta data on
* the route between client and server, the session quota provided
@ -154,14 +168,7 @@ namespace {
Arg_string::set_arg(argbuf, sizeof(argbuf), "cap_quota",
String<32>(cap_quota).string());
Session_capability cap =
_parent.session(id, name, Parent::Session_args(argbuf), affinity);
if (cap.valid())
return cap;
_block_for_session();
return _parent.session_cap(id);
return try_session(name, id, Parent::Session_args(argbuf), affinity);
}
catch (Insufficient_ram_quota) {

View File

@ -54,10 +54,22 @@ struct Qt_launchpad_namespace::Local_env : Genode::Env
Parent::Client::Id id,
Parent::Session_args const &session_args,
Affinity const &affinity) override
{ return genode_env.session(service_name, id, session_args, affinity); }
{
return genode_env.session(service_name, id, session_args, affinity);
}
Session_capability try_session(Parent::Service_name const &service_name,
Parent::Client::Id id,
Parent::Session_args const &session_args,
Affinity const &affinity) override
{
return genode_env.try_session(service_name, id, session_args, affinity);
}
void upgrade(Parent::Client::Id id, Parent::Upgrade_args const &args) override
{ return genode_env.upgrade(id, args); }
{
return genode_env.upgrade(id, args);
}
void close(Parent::Client::Id id) override { return genode_env.close(id); }
@ -72,6 +84,7 @@ struct Qt_launchpad_namespace::Local_env : Genode::Env
}
};
void Libc::Component::construct(Libc::Env &env)
{
Libc::with_libc([&] {

View File

@ -114,6 +114,12 @@ class Libc::Env_implementation : public Libc::Env, public Config_accessor
Affinity const &aff) override {
return _env.session(name, id, args, aff); }
Session_capability try_session(Parent::Service_name const &name,
Parent::Client::Id id,
Parent::Session_args const &args,
Affinity const &aff) override {
return _env.try_session(name, id, args, aff); }
void upgrade(Parent::Client::Id id,
Parent::Upgrade_args const &args) override {
return _env.upgrade(id, args); }

View File

@ -40,7 +40,7 @@ class Sandbox::Abandonable : Interface
};
class Sandbox::Parent_service : public Genode::Parent_service, public Abandonable
class Sandbox::Parent_service : public Genode::Try_parent_service, public Abandonable
{
private:
@ -51,7 +51,7 @@ class Sandbox::Parent_service : public Genode::Parent_service, public Abandonabl
Parent_service(Registry<Parent_service> &registry, Env &env,
Service::Name const &name)
:
Genode::Parent_service(env, name), _reg_elem(registry, *this)
Genode::Try_parent_service(env, name), _reg_elem(registry, *this)
{ }
};

View File

@ -78,10 +78,22 @@ class Gdb_monitor::App_child : public Child_policy,
Parent::Client::Id id,
Parent::Session_args const &session_args,
Affinity const &affinity) override
{ return genode_env.session(service_name, id, session_args, affinity); }
{
return genode_env.session(service_name, id, session_args, affinity);
}
Session_capability try_session(Parent::Service_name const &service_name,
Parent::Client::Id id,
Parent::Session_args const &session_args,
Affinity const &affinity) override
{
return genode_env.session(service_name, id, session_args, affinity);
}
void upgrade(Parent::Client::Id id, Parent::Upgrade_args const &args) override
{ return genode_env.upgrade(id, args); }
{
return genode_env.upgrade(id, args);
}
void close(Parent::Client::Id id) override { return genode_env.close(id); }