RFC-0108: Component binder protocol

RFC-0108: Component binder protocol
StatusAccepted
Areas
  • Component Framework
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:

  1. A distinct type helps distinguish startup capabilities better. For example, we can promote naming conventions for binder capabilities that diverge from protocols.
  2. Consistency with other capabilities in terms of what translates to a bind (connecting == bind)
  3. 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.