RFC-0108: Component binder protocol | |
---|---|
Status | Accepted |
Areas |
|
Description | Framework-provided protocol that allows components to start other components. |
Gerrit change | |
Authors | |
Reviewers | |
Date submitted (year-month-day) | 2021-05-21 |
Date reviewed (year-month-day) | 2021-06-30 |
Summary
This RFC introduces a new framework-provided protocol,
fuchsia.component.Binder
, that will allow components to start other
components that expose it.
Motivation
The fuchsia.sys2.Realm protocol is a framework-provided API that
allows components to manipulate their realm at runtime. With this
protocol, components can create child components and bind to their exposed
capabilities, driven by runtime decisions rather than just static declarations.
Components can bind to the child's capabilities by invoking the BindChild
method. This method, upon successful execution, starts the provided child
component, if not already started, and opens a connection to an instance of
fuchsia.io.Directory
protocol that is backed by the child
component's exposed directory. The child's exposed directory is a directory
containing all capabilities exposed by the child in its manifest.
This method has two drawbacks. First, it is currently overloaded because it fulfills two use cases. It allows components to bind to their child component's capabilities, and it also allows components to start the child components. Secondly, it is incongruous with Component Framework's capability model. Most components start in order to satisfy a request for a capability. Said differently, components bind to capabilities, not to the components providing those capabilities. This is an important feature of encapsulation because if components interact with capabilities, as opposed to components directly, the implementing component can be swapped out without any changes to the client.
Consequently, BindChild
will be deprecated. A replacement
method, OpenExposedDir
, will be added to the fuchsia.sys2.Realm
protocol
that will allow parents to bind to their child's capabilities. The notable
difference between this method and BindChild
is that in the new method,
the child will be started if and only if the parent binds to one of its
capabilities. This change in semantics better aligns with Component Framework's
design principles. Work for this migration has already begun and can be tracked
at fxr/531142.
However, simply replacing BindChild
with OpenExposedDir
will not suffice.
There are a significant, in terms of quantity and importance to the platform,
number of use cases that rely on the automatic starting behavior of BindChild
.
In these instances, the parent component doesn't bind to any of the
child's exposed capabilities or the parent starts a component that doesn't
expose any capabilities. This pattern can be observed in certain integration
tests, drivers, and session elements. For this use case, the Component
Framework team must provide a solution for customers to start components.
Design
Component Framework will introduce a new framework-provided protocol,
fuchsia.component.Binder
. This capability will allow authors to declare
components as directly bindable. Components wishing to start other components
then use that protocol as they would do for any other capability. Component
Manager will be on the server end of this protocol, and once a connection is
made, it will start the component exposing this capability. The target
component's termination can be captured by observing the ZX_CHANNEL_PEER_CLOSED
on the client end of the connection.
library fuchsia.component;
/// A framework-provided protocol that allows components that use it to start the
/// component that exposes it.
///
/// Note: The component doesn't need to serve this protocol, it is implemented
/// by the framework.
[Discoverable]
protocol Binder {};
Component authors need only expose the protocol:
{
expose: [
{
protocol: "fuchsia.component.Binder",
from: "framework", // Note that this is implemented by the framework on the component's behalf.
},
],
}
Components that will start such components will do so by binding to the exposed capability:
{
use: [
{
protocol: "fuchsia.component.Binder",
from: "parent",
},
],
}
The major benefit of this proposal is that direct starting becomes part of a
component's API. Components that are directly startable, especially those that
don't expose capabilities, can be audited in manifest files. Furthermore,
starting another component isn't restricted to parent components starting
direct children. Instead, the fuchsia.component.Binder
protocol can be
routed anywhere like other capabilities.
Implementation
Implementing this design won't require many changes. Introducing this protocol should take one or two Gerrit changes.
After this feature is published, users of BindChild
will be migrated to
using OpenExposedDir
or the proposed fuchsia.component.Binder
protocol
depending on their use case.
Once all use cases of BindChild
have been migrated, the method will be
removed from the fuchsia.sys2.Realm
protocol.
Performance
This protocol will add a performance regression. Currently, parent components
may call BindChild
to start child components. After this proposal, parent
components will have to call OpenExposedDir
, then open fuchsia.component.Bind
to achieve the same effect. However, this regression should be nominal as these
events are rare anyway.
Security considerations
This protocol shouldn't raise security concerns. This protocol will be routable, so which components start which other components are auditable by inspecting the manifest files.
Privacy considerations
This protocol shouldn't raise privacy concerns as this just allows a mechanism to start other components.
Testing
This feature will be tested with unit and integration tests.
Furthermore, this feature will allow component authors to test the side-effects of other components more easily. For example, in Diagnostics, several integration tests assert the state of an Inspect VMO. In such cases, the driver component starts a puppet component that manipulates the VMO and then the driver component asserts on the contents of the VMO. The proposed feature will allow the driver component to start the puppet component without requiring extraneous FIDL protocols.
Documentation
The FIDL API will be documented and this feature will be documented more broadly in the Component Framework Realm Docs.
Drawbacks, alternatives, and unknowns
Single-run Components
A related RFC has recently been accepted that will also allow
components to start child components. That proposal will be able to satisfy some
of the use cases filled by BindChild
, but not all. Notably, components
started via the proposed mechanism must reside in a collection, whereas
fuchsia.component.Binder
will work for all components.
fuchsia.sys2.Realm/StartChild
Another option is to extend thefuchsia.sys2.Realm
protocol by adding a method
to start a child. This method, StartChild
, will take one parameter,
ChildRef
, that will be a reference to either a statically-declared child or
dynamically-created one (collections). This is the same parameter that
BindChild
receives and that OpenExposedDir
will take. This method will
return a Zircon Event object that will be signaled once the child
component stops. The lifecycle of the child component will be tied to the
parent component. If the parent component is stopped, then the child will be
stopped as well.
library fuchsia.sys2;
[Discoverable]
protocol Realm {
/// Start child component instance, if it isn't already. Returns a Zircon
/// Event object that clients can use to observe when the child component stops.
/// When the child component stops, Component Manager will set this object's
/// ZX_EVENT_SIGNALED bit.
StartChild(ChildRef child) -> (zx.handle:EVENT event) error fuchsia.component.Error;
};
Ultimately, such an alternative is undesirable because it allows any components
to be directly startable. Direct starting, as opposed to starting as a side-effect of a
capability binding, should only be allowed if a component declares it.
Since StartChild
would take a ChildRef
, it'll allow any component to be
started. With collections, what components are started is only observable at
runtime.
Bind Method
Lastly, instead of introducing the empty Binder protocol proposed above, a method can be used to trigger the start.
library fuchsia.component;
[Discoverable]
protocol Binder {
/// Start the associated component instance.
/// This method is reentrant and safe for concurrency.
/// Calling `Bind` on an already-binded component is a no-op.
/// When the child component stops, the `ZX_EVENTPAIR_PEER_CLOSED` signal
/// will be asserted on the `event` object.
Bind(zx.handle:EVENTPAIR event) -> () error fuchsia.component.Error;
};
While this option makes the start trigger more explicit, i.e. as a response to a method call as opposed to connection to a protocol, it's less viable than the proposal because it adds extra hurdles for clients. The have to setup an event pair object before calling the method without getting any added functionality.
Binder Capability
Component Framework can also introduce a new capability that is backed by the
proposed fuchsia.component.Binder
protocol.
// a.cml
{
capabilities: [
{
binder: "a",
},
],
expose: [
{
binder: "a",
from: "self",
},
],
}
A new capability provides many benefits, such as:
- A distinct type helps distinguish startup capabilities better. For example, we can promote naming conventions for binder capabilities that diverge from protocols.
- Consistency with other capabilities in terms of what translates to a bind (connecting == bind)
- No chance of forgetting the
framework
keyword (although we could add cmc checks for this).
However, despite the benefits, introducing a new capability adds conceptual overhead, i.e. a new thing for developers to learn and understand and for CF to support. A framework-provided protocol is more straightforward and easier to conceptualize.