Availability

To select what capabilities go in its namespace , a component specifies a use declaration for each of those capabilities in its manifest. However, since capability routing involves a chain of components, just because a component uses a capability doesn't mean it will always be available. It can be unavailable for various reasons: for example, if one of the components in the chain didn't route the capability, or if one of those components failed to resolve.

When a component uses or routes a capability, it may have various expectations around whether the capability must be available. In some cases, a capability is essential to a component's operation: if someone creates a topology is that fails to route the capability to a component, something should ideally detect that failure as early as possible to prevent a bug. However, in other cases, the component may be tolerant of the capability's absence, or the capability may be expected to be absent in certain configurations. In order to accommodate these different scenarios, cml provides an availability option on use, offer, and expose which the component can use to declare its expectation about a capability's availability.

Options

The framework supports the following options for availability:

Required

The most commonly used availability option is required, which indicates that the component always expects the capability to be available, and cannot be expected to function properly if it's absent. This is the default option that is automatically set if no availability is specified.

Optional

optional signifies that a capability may not be present on some configurations. If a component uses an optional capability that turns out to be unavailable, the component should be able to function without it, for example by deactivating any features requiring that capability.

However, if an optional capability is not available on some configuration, the framework expects that the topology still includes a complete route for the capability that terminates in void. In this way, the product owner is expected to make an explicit choice that either the capability is available (its route terminates at some provider component) or it is not (its route terminates at void). If the route is incomplete, tooling and diagnostics will report this as an error.

Transitional

transitional is like a weaker version of optional. Like optional, if a component uses a transitional capability, it should be tolerant to the absence of the capability. However, unlike optional, a transitional route does not need to terminate in void and tooling and diagnostics will not report any error if a transitional route is incomplete or invalid.

The name "transitional" connotes the idea that this option is useful for soft transitions. For example, suppose you have an Echo protocol, that you want to replace with EchoV2. At an early step in this transition, you can change the client to use the new protocol with transitional availability. Later, once an end-to-end route has been established, you can upgrade the availability to required or optional. If you had tried to use optional instead, tooling and diagnostics would complain that the client component's parent was missing a capability route for Echo2, while transitional will suppress such warnings.

Same as target

same_as_target acts as a passthrough: it causes the availability to be inherited from whatever availability was set by the route's target component. This is useful for intermediate components that pass a lot of capabilities through, so that when the availability of a route is changed in the target, no change is required in the source.

Because the component that uses a capability is its final destination, same_as_target is not a valid option for use.

Availability comparisons

There is a relative ordering of "strength" between the availability options. From strongest to weakest, the order is: required > optional > transient. same_as_target is not part of this because it's a passthrough: the strength of a same_as_target capability route is effectively equal to whatever availability it inherited from the target.

It is valid, and useful in many cases, to downgrade the availability from the source to the target. For example, if component X exposes a required capability to Y, Y may choose to use the capability as optional, or offer the capability as optional to another child. However, it is a routing error to upgrade the availability from the source. If this happens, any attempt to use or route the capability will fail, in the same way it would fail if the routing chain were incomplete. This is an error because it means you are trying to establish stronger guarantees on the availability than what the source has guaranteed. For example, it doesn't make sense for a component to use a capability as required if its parent offered the capability as optional, since that means the parent has declared the capability might not be available.

Tooling and diagnostics

The main effect of setting availability is to control how host tooling and diagnostics reports routing errors. In a nutshell, this means the weaker the availability, the less strict they will be. Here's the detailed specification:

  • If a required capability route is incomplete, invalid, or ends in void:
    • Scrutiny will report this as a routing error. This will cause a build error if the build configuration required this to pass.
    • component_manager will log a WARNING message in the using component's scope describing the routing error.
  • If an optional capability route is incomplete or invalid:
    • Scrutiny will report this as a routing error. This will cause a build error if the build configuration required this to pass.
    • component_manager will log an INFO message in the using component's scope describing the routing error.
  • If an optional capability route is ends in void:
    • Scrutiny will not report an error.
    • component_manager may log an INFO message, but no error.
  • If a transitional capability route is incomplete, invalid, or ends in void:
    • No error or other information is logged.

For general runtime behavior, availability has no effect. For example, suppose a component attempts to use a protocol in its namespace whose route is broken. The end result will be the same if the protocol was routed as required or optional:

  • The protocol will appear in the component's namespace.
  • The initial call to connect to the capability will succeed (assuming a standard one-way API is used like connect_to_protocol in rust).
  • component_manager will attempt to route the capability. In the end, routing will fail, and the channel will be closed with a NOT_FOUND epitaph.

source_availability: unknown

cml has an additional feature which can be used to autogenerate either a required or optional void offer, depending on whether a cml shard is included that has a child declaration for the source. More information on this feature is in the build docs.

Example

The following example illustrates the concepts described in this doc.

In this example, there are three components:

  • echo_client, which tries to consume various protocols provided by echo_server
  • echo_server, which provides some protocols
  • echo_realm, the parent of echo_client and echo_server which links them together

Let's examine their cml files:

// echo_client.cml
{
    ...
    use: [
        { protocol: "fuchsia.example.Echo" },
        {
            protocol: "fuchsia.example.EchoV2",
            availability: "transitional",
        },
        {
            protocol: "fuchsia.example.Stats",
            availability: "optional",
        },
    ],
}

// echo_server.cml
{
    ...
    capabilities: [
        { protocol: "fuchsia.example.Echo" },
    ],
    expose: [
        {
            protocol: "fuchsia.example.Echo",
            from: "self",
        },
    ],
}

// echo_realm.cml
{
    offer: [
        {
            protocol: "fuchsia.example.Echo",
            from: "#echo_server",
            to: "#echo_client",
            availability: "required",
        },
        {
            protocol: "fuchsia.example.Stats",
            from: "void",
            to: "#echo_client",
            availability: "optional",
        },
    ],
    children: [
        {
            name: "echo_server",
            url: "echo_server#meta/echo_server.cm",
        },
        {
            name: "echo_client",
            url: "echo_client#meta/echo_client.cm",
        },
    ],
}

Recall that when availability is omitted, it defaults to required. With this topology, the behavior is as follows:

  • echo_client will be able to successfully connect to fuchsia.example.Echo.
  • echo_client would fail to connect to fuchsia.example.EchoV2 or fuchsia.example.Stats.
  • Tooling and diagnostics will not log an error.
    • No error for fuchsia.example.Stats because it's optional and routes to void.
    • No error for fuchsia.example.Stats because it's transient.

Now, let's see what happens with a different version:

// echo_client.cml
{
    ...
    use: [
        { protocol: "fuchsia.example.Echo" },
        {
            protocol: "fuchsia.example.EchoV2",
            availability: "transitional",
        },
        {
            protocol: "fuchsia.example.Stats",
            availability: "optional",
        },
    ],
}

// echo_server.cml
{
    ...
    capabilities: [
        {

            protocol: [
                "fuchsia.example.Echo",
                "fuchsia.example.EchoV2",
                "fuchsia.example.Stats",
            ],
        },
    ],
    expose: [
        {
            protocol: [
                "fuchsia.example.Echo",
                "fuchsia.example.EchoV2",
            ],
            from: "self",
        },
        {
            protocol: "fuchsia.example.Stats",
            from: "self",
            availability: "optional",
        },
    ],
}

// echo_realm.cml
{
    offer: [
        {
            protocol: [
                "fuchsia.example.Echo",
                "fuchsia.example.EchoV2",
            ],
            from: "#echo_server",
            to: "#echo_client",
            availability: "same_as_target",
        },
        {
            protocol: "fuchsia.example.Stats",
            from: "#echo_server",
            to: "#echo_client",
            availability: "optional",
        },
    ],
    children: [
        {
            name: "echo_server",
            url: "echo_server#meta/echo_server.cm",
        },
        {
            name: "echo_client",
            url: "echo_client#meta/echo_client.cm",
        },
    ],
}

Now:

  • echo_client will be able to successfully connect to fuchsia.example.Echo, fuchsia.example.EchoV2, and fuchsia.example.Stats.
    • Each route, while having different availability, is complete and terminates in a real component.
    • For each route, availability between source and target passes the comparison check
  • Tooling and diagnostics will not log an error, because all routes complete.