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; 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 &, virtual Session_capability session(Parent::Service_name const &,
Parent::Client::Id, Parent::Client::Id,
Parent::Session_args const &, Parent::Session_args const &,
@ -82,10 +92,6 @@ struct Genode::Env : Interface
* \param affinity preferred CPU affinity for the session * \param affinity preferred CPU affinity for the session
* *
* \throw Service_denied * \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'. * See the documentation of 'Parent::session'.
* *
@ -161,6 +167,20 @@ struct Genode::Env : Interface
* \noapi * \noapi
*/ */
virtual void reinit_main_thread(Capability<Region_map> &stack_area_rm) = 0; 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_ */ #endif /* _INCLUDE__BASE__ENV_H_ */

View File

@ -27,6 +27,7 @@ namespace Genode {
class Service; class Service;
template <typename> class Session_factory; template <typename> class Session_factory;
template <typename> class Local_service; template <typename> class Local_service;
class Try_parent_service;
class Parent_service; class Parent_service;
class Async_service; class Async_service;
class Child_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: private:
@ -240,12 +246,13 @@ class Genode::Parent_service : public Service
public: public:
/** Try_parent_service(Env &env, Service::Name const &name)
* Constructor
*/
Parent_service(Env &env, Service::Name const &name)
: Service(name), _env(env) { } : Service(name), _env(env) { }
/*
* \throw Out_of_ram
* \throw Out_of_caps
*/
void initiate_request(Session_state &session) override void initiate_request(Session_state &session) override
{ {
switch (session.phase) { switch (session.phase) {
@ -256,7 +263,7 @@ class Genode::Parent_service : public Service
_env.id_space()); _env.id_space());
try { try {
session.cap = _env.session(name().string(), session.cap = _env.try_session(name().string(),
session.id_at_parent->id(), session.id_at_parent->id(),
Session_state::Server_args(session).string(), Session_state::Server_args(session).string(),
session.affinity()); session.affinity());
@ -265,23 +272,26 @@ class Genode::Parent_service : public Service
} }
catch (Out_of_ram) { catch (Out_of_ram) {
session.id_at_parent.destruct(); session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; } session.phase = Session_state::CLOSED;
throw;
}
catch (Out_of_caps) { catch (Out_of_caps) {
session.id_at_parent.destruct(); session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; } session.phase = Session_state::CLOSED;
throw;
}
catch (Insufficient_ram_quota) { catch (Insufficient_ram_quota) {
session.id_at_parent.destruct(); session.id_at_parent.destruct();
session.phase = Session_state::INSUFFICIENT_RAM_QUOTA; } session.phase = Session_state::INSUFFICIENT_RAM_QUOTA;
}
catch (Insufficient_cap_quota) { catch (Insufficient_cap_quota) {
session.id_at_parent.destruct(); session.id_at_parent.destruct();
session.phase = Session_state::INSUFFICIENT_CAP_QUOTA; } session.phase = Session_state::INSUFFICIENT_CAP_QUOTA;
}
catch (Service_denied) { catch (Service_denied) {
session.id_at_parent.destruct(); session.id_at_parent.destruct();
session.phase = Session_state::SERVICE_DENIED; } session.phase = Session_state::SERVICE_DENIED;
}
break; 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 * Representation of a service that asynchronously responds to session request
*/ */

View File

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

View File

@ -54,10 +54,22 @@ struct Qt_launchpad_namespace::Local_env : Genode::Env
Parent::Client::Id id, Parent::Client::Id id,
Parent::Session_args const &session_args, Parent::Session_args const &session_args,
Affinity const &affinity) override 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 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); } 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) void Libc::Component::construct(Libc::Env &env)
{ {
Libc::with_libc([&] { Libc::with_libc([&] {

View File

@ -114,6 +114,12 @@ class Libc::Env_implementation : public Libc::Env, public Config_accessor
Affinity const &aff) override { Affinity const &aff) override {
return _env.session(name, id, args, aff); } 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, void upgrade(Parent::Client::Id id,
Parent::Upgrade_args const &args) override { Parent::Upgrade_args const &args) override {
return _env.upgrade(id, args); } 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: private:
@ -51,7 +51,7 @@ class Sandbox::Parent_service : public Genode::Parent_service, public Abandonabl
Parent_service(Registry<Parent_service> &registry, Env &env, Parent_service(Registry<Parent_service> &registry, Env &env,
Service::Name const &name) 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::Client::Id id,
Parent::Session_args const &session_args, Parent::Session_args const &session_args,
Affinity const &affinity) override 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 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); } void close(Parent::Client::Id id) override { return genode_env.close(id); }