This document demonstrates how to connect components together using capabilities and additional tools for parent components to manage their children.
Concepts
You should understand the following concepts before continuing with this guide:
- The Component Framework assembles the namespace for a component using component declarations that describe the capabilities the component requires to function. The capabilities the component exposes to others are assembled into an exposed directory .
- Every component receives a handle to the server end of a
Directory
channel called the outgoing directory . The component's program makes discoverable any capabilities that it provides through this directory. - At runtime, the component instance tree describes parent and child relationships between component instances . The component instance tree and the capability routes over that tree are collectively referred to as the component topology .
- Parent components declare child components either statically in their
component manifest
or dynamically using a
component collection
. A collection is a
container for dynamic children that may be created and destroyed at runtime
using the
fuchsia.component.Realm
framework protocol.
For more details on these concepts, see Realms and Capabilities.
Connecting capabilities through routing
Components interact with each other through capabilities. Capabilities served by
a component need to be declared in that component's manifest and, for them to be
usable by others, routed to parent/child components through expose
and offer
declarations. Other components that use that capability also need to declare
their use in their manifests. In order for their programs to use the capability
at runtime, the used capability must be routed to that component - either
offered from a parent or exposed by a child.
Capability routing refers to the recursive process, performed by the component manager, of identifying a serving component by following individual routing steps described in manifest files. Capability routing is initiated when:
- A component's program opens a path in its namespace .
- A component's program opens a path in another component's exposed directory .
- A developer invokes
ffx component route
. - Starting a component that depends on a Resolver or Runner whose backing resolver or runner capabilities have not been routed.
Routing is performed lazily: while a capability may be configured to be provided by a parent or child (directly or indirectly through further delegation), the target component may not have been resolved yet when the routing operation is initiated. Practically, this means that the full route from the requesting component to the final serving component may not be known until routing is attempted.
Provide a capability implementation
Components that provide a capability must declare the capability in their
component manifest using a capabilities
declaration.
See the following example that declares a FIDL protocol capability in the providing component's manifest:
{
include: [
"inspect/client.shard.cml",
"syslog/client.shard.cml",
],
// Information about the program to run.
program: {
// Use the built-in ELF runner.
runner: "elf",
// The binary to run for this component.
binary: "bin/echo_server_rust",
},
// Capabilities provided by this component.
capabilities: [
{ protocol: "fidl.examples.routing.echo.Echo" },
],
expose: [
{
protocol: "fidl.examples.routing.echo.Echo",
from: "self",
},
],
}
At runtime, the server component provides an implementation of the capability by serving it in its outgoing directory using the fuchsia.io protocol. The generated FIDL bindings wrap this handle and enable the provider to begin receiving incoming requests:
Rust
// Wrap protocol requests being served.
enum IncomingRequest {
Echo(EchoRequestStream),
}
#[fuchsia::main(logging = false)]
async fn main() -> Result<(), anyhow::Error> {
let mut service_fs = ServiceFs::new_local();
// Initialize inspect
component::health().set_starting_up();
let _inspect_server_task = inspect_runtime::publish(
component::inspector(),
inspect_runtime::PublishOptions::default(),
);
// Serve the Echo protocol
service_fs.dir("svc").add_fidl_service(IncomingRequest::Echo);
service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;
// Component is serving and ready to handle incoming requests
component::health().set_ok();
// Attach request handler for incoming requests
service_fs
.for_each_concurrent(None, |request: IncomingRequest| async move {
match request {
IncomingRequest::Echo(stream) => handle_echo_request(stream).await,
}
})
.await;
Ok(())
}
// Handler for incoming service requests
async fn handle_echo_request(mut stream: EchoRequestStream) {
while let Some(event) = stream.try_next().await.expect("failed to serve echo service") {
let EchoRequest::EchoString { value, responder } = event;
responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
}
}
C++
// Handler for incoming service requests
class EchoImplementation : public fidl::examples::routing::echo::Echo {
public:
void EchoString(fidl::StringPtr value, EchoStringCallback callback) override { callback(value); }
fidl::examples::routing::echo::Echo_EventSender* event_sender_;
};
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
// Initialize inspect
inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
inspector.Health().StartingUp();
// Serve the Echo protocol
EchoImplementation echo_instance;
fidl::Binding<fidl::examples::routing::echo::Echo> binding(&echo_instance);
echo_instance.event_sender_ = &binding.events();
fidl::InterfaceRequestHandler<fidl::examples::routing::echo::Echo> handler =
[&](fidl::InterfaceRequest<fidl::examples::routing::echo::Echo> request) {
binding.Bind(std::move(request));
};
context->outgoing()->AddPublicService(std::move(handler));
// Component is serving and ready to handle incoming requests
inspector.Health().Ok();
return loop.Run();
}
Connect to routed capabilities
Client components declare capabilities they may request in their component
manifest with a use
declaration.
See the following example of a client component's manifest that uses the FIDL protocol provided by the previous component:
{
include: [
// Enable logging on stdout
"syslog/client.shard.cml",
],
// Information about the program to run.
program: {
// Use the built-in ELF runner.
runner: "elf",
// The binary to run for this component.
binary: "bin/echo_client_rust",
// Program arguments
args: [ "Hello Fuchsia!" ],
},
// Capabilities used by this component.
use: [
{ protocol: "fidl.examples.routing.echo.Echo" },
],
}
At runtime, the client component opens paths populated in its namespace using the fuchsia.io protocol in order to acquire capabilities provided by other components. The Fuchsia component library works with the generated FIDL bindings to provide a structured interface for communicating over the channel:
Rust
#[fuchsia::main]
async fn main() -> Result<(), anyhow::Error> {
// Parse arguments, removing binary name
let mut args: Vec<String> = std::env::args().collect();
args.remove(0);
// Connect to FIDL protocol
let echo = connect_to_protocol::<EchoMarker>().expect("error connecting to echo");
// Send messages over FIDL interface
for message in args {
let out = echo.echo_string(Some(&message)).await.expect("echo_string failed");
tracing::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
}
Ok(())
}
C++
int main(int argc, const char* argv[], char* envp[]) {
// Set tags for logging.
fuchsia_logging::LogSettingsBuilder builder;
builder.WithTags({"echo_client"}).BuildAndInitialize();
// Connect to FIDL protocol
fidl::examples::routing::echo::EchoSyncPtr echo_proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(echo_proxy.NewRequest());
// Send messages over FIDL interface for each argument
fidl::StringPtr response = nullptr;
for (int i = 1; i < argc; i++) {
ZX_ASSERT(echo_proxy->EchoString(argv[i], &response) == ZX_OK);
if (!response.has_value()) {
FX_LOG_KV(INFO, "echo_string got empty result");
} else {
FX_LOG_KV(INFO, "Server response", FX_KV("response", response->c_str()));
}
}
return 0;
}
It is the responsibility of the component's parent to route to it all necessary capabilities.
Mark some used capabilities as optional
Not all capabilities used by a component are required for it to operate successfully. Sometimes a component can still execute without issue if a capability is missing, and its presence will merely enable some additional or alternative behavior.
To enable the component framework to understand which capabilities a component
requires and which capabilities are optional for a component, use the
availability
field.
use: [
{
// It is ok if this protocol is unavailable
protocol: "fuchsia.examples.Echo1",
availability: "optional",
},
{
// This protocol MUST be provided for the component to function correctly.
protocol: "fuchsia.examples.Echo2",
availability: "required",
},
]
If a component has a required
use declaration for a capability (the default)
but its parent offers the capability as optional
, then the
static capability analyzer will generate an error and
connection attempts at runtime will always fail.
Consuming optional capabilities
In the case that a component's parent has offer
ed a capabilitity with
availability: "optional"
, the capability may not be usable at runtime.
An entry in the component's
namespace
will be present
whether the capability is available or not. Any attempt to open the path for
that capability will result in the handle provided to the Directory.Open()
call being closed with a ZX_ERR_NOT_FOUND
epitaph.
Usage of libc
methods like open()
or stat()
will return ENOENT
.
Route capabilities
Components may only access capabilities routed to them. Capabilities can originate from anywhere in the component topology as long as a valid capability route exists as a chain of the following declarations from the capability provider to any consumers:
expose
: Routes a capability up to the component's parent.offer
: Routes a capability down to one of the component's children.
To connect capability providers with components requesting those capabilities, do the following:
Add an
offer
orexpose
declaration to the capability provider component:{ include: [ "inspect/client.shard.cml", "syslog/client.shard.cml", ], // Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/echo_server_rust", }, // Capabilities provided by this component. capabilities: [ { protocol: "fidl.examples.routing.echo.Echo" }, ], expose: [ { protocol: "fidl.examples.routing.echo.Echo", from: "self", }, ], }
For each intermediate component in the component instance tree, include additional
expose
andoffer
declarations until you reach the consuming component containing ause
declaration:{ // Two children: a server and client. children: [ { name: "echo_server", url: "#meta/echo_server.cm", }, { name: "echo_client", url: "#meta/echo_client.cm", }, ], offer: [ // Route Echo protocol from server to client. { protocol: "fidl.examples.routing.echo.Echo", from: "#echo_server", to: "#echo_client", }, // Route diagnostics protocols to both children. { protocol: [ "fuchsia.inspect.InspectSink", "fuchsia.logger.LogSink", ], from: "parent", to: [ "#echo_client", "#echo_server", ], }, { dictionary: "diagnostics", from: "parent", to: [ "#echo_client", "#echo_server", ], }, ], }
Optional dependencies
When a component has an optional dependency on a capability, it is then up to
that component's parent to decide if the component will receive that capability
or not. When offering a capability, a component may set the availability
field
to either optional
, required
, or same_as_target
. Each value has the
following semantics:
optional
: The target of the offer must declare its ability to handle the absence of this capability by marking it'suse
declaration asoptional
. If the target cannot do this, (i.e. the target has an availability ofrequired
for this capability), then routing the capability will cause an error.required
: The target must receive this capability. If the offer source isparent
and the component's parent (the target's grandparent) offered this as an optional capability, then routing the capability will cause an error because the parent cannot guarantee the capability's availability.same_as_target
: The availability of this capability is determined by the target's expectations. If the target has an optional dependency on this capability, then this offer will also be optional. If the target has a required dependency on this capability, then this offer will also be required.
offer: [
{
// child-1 MUST receive the protocol 'fuchsia.logger.LogSink'.
protocol: "fuchsia.logger.LogSink",
to: "#child-1",
from: "#child-2",
availability: "required",
},
{
// child-1 MUST be able to handle the absence of the protocol
// 'fuchsia.tracing.provider.Registry'.
protocol: "fuchsia.tracing.provider.Registry",
to: "#child-1",
from: "parent",
availability: "optional",
},
{
// child-1 decides if it must receive the protocol
// 'fuchsia.posix.socket.Provider', or if it must be able to support its
// absence.
protocol: "fuchsia.posix.socket.Provider",
to: "#child-1",
from: "parent",
availability: "same_as_target",
},
]
Like with use declarations, the availability
field may be omitted, in which
case it defaults to required
.
Transitionally dependencies
The component framework allows components to soft-transition into using and offering both optional and required capabilities. With the transitional availability marker, a component using a capability, will not cause Scrutiny validation errors whether or not the parent is required, optional, or same as target. Note, though this field exists to enable soft-transitions, components should ultimately settle on either optional or required.
To use this feature, the child component will mark its availability as "transitional":
use: [
{
// It is ok if this protocol is offered as "required" or "optional"
protocol: "fuchsia.examples.Echo",
availability: "transitional",
},
]
Managing child components
Components can interact with each other from anywhere in the component topology through capabilities as long as a valid capability route exists between them. There are additional methods that enable parent components to interact with their direct children.
The following example component declares a single static child named lifecycle
and a collection named echo
where additional child components may be created
at runtime:
{
// ...
// Statically declared child components
children: [
{
name: "lifecycle",
url: "lifecycle#meta/default.cm",
},
],
// Collection to hold dynamically created child components
collections: [
{
name: "echo",
durability: "transient",
},
],
// Capabilities required by this component
use: [
{
protocol: "fuchsia.component.Binder",
from: "#lifecycle",
},
{
protocol: "fuchsia.component.Realm",
from: "framework",
},
],
// Capabilities required by child components
offer: [
{
protocol: [ "fuchsia.inspect.InspectSink" ],
from: "parent",
to: [
"#echo",
"#lifecycle",
],
},
],
}
Notice that a collection behaves like a static child instance in the parent component's manifest — you can give it a name and offer specific capabilities to it. All child components in the collection may access the set of capabilities offered to it.
Start child components
The Component Framework provides the fuchsia.component.Binder
protocol for parent components to explicitly start a child that may not expose
any other capabilities. Since this capability is provided by the framework,
child components only need to expose it from their component manifest:
{
// ...
// Capabilities exposed from this component to parent.
expose: [
{
// Expose this protocol so that parent component can start it
// by binding to this capability.
protocol: "fuchsia.component.Binder",
from: "framework",
},
],
}
Create dynamic children
To create a new child component at runtime, use the
fuchsia.component.Realm
protocol to create the component inside
of an existing collection. Call the CreateChild
method with the following parameters:
CollectionRef
: Describes the collection where the component will be added.Child
: Component declaration, including its name and component URL.
Rust
use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};
// ...
// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
async fn create_dynamic_child() {
let realm = client::connect_to_protocol::<RealmMarker>()
.expect("failed to connect to fuchsia.component.Realm");
let collection_ref = CollectionRef { name: String::from("echo") };
let child_decl = Child {
name: Some(String::from("lifecycle_dynamic")),
url: Some(String::from("echo_server#meta/default.cm")),
startup: Some(StartupMode::Lazy),
..Default::default()
};
realm
.create_child(&collection_ref, &child_decl, CreateChildArgs::default())
.await
.expect("create_child failed")
.expect("failed to create child");
}
C++
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
// ...
// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
void CreateDynamicChild() {
fuchsia::component::decl::CollectionRef collection_ref = {
.name = "echo",
};
fuchsia::component::decl::Child child_decl;
child_decl.set_name("lifecycle_dynamic");
child_decl.set_url("echo_server#meta/default.cm");
child_decl.set_startup(fuchsia::component::decl::StartupMode::LAZY);
realm_proxy_->CreateChild(std::move(collection_ref), std::move(child_decl),
fuchsia::component::CreateChildArgs(),
[&](fuchsia::component::Realm_CreateChild_Result result) {
ZX_ASSERT(!result.is_err());
FX_LOGS(INFO) << "Dynamic child instance created.";
ConnectDynamicChild();
});
}
Connect to child capabilities
Because the parent of a dynamic component is not known at build time, its exposed capabilities cannot be named in capability routes expressed in the component manifest.
To connect with the capabilities exposed by a dynamic child instance:
Use the
fuchsia.component.Realm
protocol to open the child's exposed directory. Call theOpenExposedDir
method with the child component's name and the collection name:Rust
use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker}; use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode}; // ... // Connect to the fidl.examples.routing.echo capability exposed by the child // instance, and send a request. async fn connect_dynamic_child(message: String) { // Open the child's exposed directory let exposed_dir = client::open_childs_exposed_directory( String::from("lifecycle_dynamic"), Some(String::from("echo")), ) .await .expect("failed to open exposed directory"); // ... }
C++
#include <fuchsia/component/cpp/fidl.h> #include <fuchsia/component/decl/cpp/fidl.h> // ... // Use the fuchsia.component.Realm protocol to open the exposed directory of // the dynamic child instance. void ConnectDynamicChild() { fuchsia::component::decl::ChildRef child_ref = { .name = "lifecycle_dynamic", .collection = "echo", }; fidl::InterfaceHandle<fuchsia::io::Directory> exposed_dir; realm_proxy_->OpenExposedDir( child_ref, exposed_dir.NewRequest(), [this, exposed_dir = std::move(exposed_dir)]( fuchsia::component::Realm_OpenExposedDir_Result result) mutable { ZX_ASSERT(!result.is_err()); std::shared_ptr<sys::ServiceDirectory> svc = std::make_shared<sys::ServiceDirectory>( sys::ServiceDirectory(std::move(exposed_dir))); SendEchoRequest(svc); }); }
Connect to the child's exposed capabilities using the exposed directory handle as the root:
Rust
// Access the fidl.examples.routing.echo capability provided there let echo = client::connect_to_protocol_at_dir_root::<EchoMarker>(&exposed_dir) .expect("failed to connect to fidl.examples.routing.echo"); let response = echo .echo_string(Some(&message)) .await .expect("echo_string failed") .expect("echo_string got empty result"); info!("Server response: {}", response);
C++
// Connect to the fidl.examples.routing.echo capability exposed by the child's // service directory. void SendEchoRequest(std::shared_ptr<sys::ServiceDirectory> svc_directory) { // Connect to the protocol inside the child's exposed directory svc_directory->Connect(echo_proxy_.NewRequest()); // Send a protocol request echo_proxy_->EchoString(message_, [&](fidl::StringPtr response) { FX_LOGS(INFO) << "Server response: " << response; DestroyDynamicChild(); }); }
Destroy dynamic children
When the dynamic child is no longer needed, use the
fuchsia.component.Realm
protocol to destroy the component
instance. Call the DestroyChild
method with a
ChildRef
representing the child inside the collection.
Rust
use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};
// ...
// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
async fn destroy_dynamic_child() {
let realm = client::connect_to_protocol::<RealmMarker>()
.expect("failed to connect to fuchsia.component.Realm");
let child_ref = ChildRef {
name: String::from("lifecycle_dynamic"),
collection: Some(String::from("echo")),
};
realm
.destroy_child(&child_ref)
.await
.expect("destroy_child failed")
.expect("failed to destroy child");
}
C++
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>
// ...
// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
void DestroyDynamicChild() {
fuchsia::component::decl::ChildRef child_ref = {
.name = "lifecycle_dynamic",
.collection = "echo",
};
realm_proxy_->DestroyChild(std::move(child_ref),
[&](fuchsia::component::Realm_DestroyChild_Result result) {
ZX_ASSERT(!result.is_err());
FX_LOGS(INFO) << "Dynamic child instance destroyed.";
// Terminate the loop
loop_->Quit();
});
}
This causes the component to stop if it is currently running. To handle this event in your component, see listen for stop events.
Controlling component lifecycle
The Component Framework provides features to modify and interact with various parts of the component lifecycle.
For more details on lifecycle concepts, see Component lifecycle.
Lifecycle notifications
The ELF runner notifies components of lifecycle events using the
fuchsia.process.lifecycle.Lifecycle
protocol.
To listen for stop notifications in your child component:
Subscribe to the lifecycle event in your component manifest:
Rust
// Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/lifecycle_example_rust", // Subscribe to component lifecycle events lifecycle: { stop_event: "notify" }, },
C++
// Information about the program to run. program: { // Use the built-in ELF runner. runner: "elf", // The binary to run for this component. binary: "bin/lifecycle_example_cpp", // Subscribe to component lifecycle events lifecycle: { stop_event: "notify" }, },
Register a lifecycle handler using the startup handle provided by the runner:
Rust
use fidl_fuchsia_process_lifecycle::{LifecycleRequest, LifecycleRequestStream}; // ... #[fuchsia::main(logging_tags = ["lifecycle", "example"])] async fn main() { // Take the lifecycle handle provided by the runner match fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::Lifecycle, 0)) { Some(lifecycle_handle) => { info!("Lifecycle channel received."); // Begin listening for lifecycle requests on this channel let x: zx::Channel = lifecycle_handle.into(); let async_x = AsyncChannel::from(fuchsia_async::Channel::from_channel(x)); let mut req_stream = LifecycleRequestStream::from_channel(async_x); info!("Awaiting request to close..."); if let Some(request) = req_stream.try_next().await.expect("Failure receiving lifecycle FIDL message") { match request { LifecycleRequest::Stop { control_handle: c } => { info!("Received request to stop. Shutting down."); c.shutdown(); process::exit(0); } } } // We only arrive here if the lifecycle channel closed without // first sending the shutdown event, which is unexpected. process::abort(); } None => { // We did not receive a lifecycle channel, exit abnormally. error!("No lifecycle channel received, exiting."); process::abort(); } } }
C++
#include <fuchsia/process/lifecycle/cpp/fidl.h> // ... // Implementation of the fuchsia.process.lifecycle FIDL protocol class LifecycleHandler : public fuchsia::process::lifecycle::Lifecycle { public: explicit LifecycleHandler(async::Loop* loop) : loop_(loop) { // Get the PA_LIFECYCLE handle, and instantiate the channel with it zx::channel channel = zx::channel(zx_take_startup_handle(PA_LIFECYCLE)); // Bind to the channel and start listening for events bindings_.AddBinding( this, fidl::InterfaceRequest<fuchsia::process::lifecycle::Lifecycle>(std::move(channel)), loop_->dispatcher()); FX_LOGS(INFO) << "Lifecycle channel received."; } // This is the Stop event we must override - see the pure virtual function we // need to override at the declaration of fuchsia::process::lifecycle::Lifecycle void Stop() override { FX_LOGS(INFO) << "Received request to stop. Shutting down."; // Shut down our loop - it's important to call Shutdown() here vs. Quit() loop_->Shutdown(); // Close the binding bindings_.CloseAll(); } private: async::Loop* loop_; fidl::BindingSet<fuchsia::process::lifecycle::Lifecycle> bindings_; };
Start with parent
Component manifests let you mark a child as
eager
, which causes the component framework to implicitly
start that child with the parent.
If the eager child fails to start for any reason (such as a missing component), component manager exhibits the following behavior:
- If the parent is not the root component, the parent will start but the component that bound to it will observe a dropped connection (just like any other failed binding).
If the parent is the root component, component manager will crash, with an error message like:
[component_manager] ERROR: protocol `fuchsia.component.CoreBinder` was not available for target `startup`: failed to resolve `fuchsia-pkg://fuchsia.com/your_component#meta/your_component.cm`: package not found: remote resolver responded with PackageNotFound For more, run `ffx component doctor `startup`
Components marked as eager
can cause system crashes when they are not present
if their ancestors are also marked eager
up to the root component. This is
important because many build configurations create system images containing a
subset of the available components. To avoid this problem, declare these
components using core realm shards to ensure they can be
safely excluded from test builds and product images.
An eager
component should also be in the same package set
as its parent since the component will be started at the same time as its
parent. Typically, eager
components should be in the product's base package
set.
To determine if your package is in the base package set, run the following command:
fx list-packages --verbose my-package
This command outputs a list of the package sets where the matching package is
found. For example, system-update-checker
is in the base
and universe
package sets:
$ fx list-packages --verbose system-update-checker
system-update-checker [base universe]
You can also look at all the packages in the base package set using the --base
option:
fx list-packages --base
Reboot on terminate
Component manifests let you control the termination policy of
your component using on_terminate
. Components with the
"reboot-on-terminate" policy set cause the system to gracefully reboot if the
component terminates for any reason other than successful exit.
To enable this feature, do the following:
Mark the child as
on_terminate: "reboot"
in the parent's component manifest:// core.cml { children: [ ... { name: "system-update-checker", url: "fuchsia-pkg://fuchsia.com/system-update-checker#meta/system-update-checker.cm", startup: "eager", on_terminate: "reboot", }, ], }
Add the component's moniker to component manager's security policy allowlist at
//src/security/policy/component_manager_policy.json5
:// //src/security/policy/component_manager_policy.json5 { security_policy: { ... child_policy: { reboot_on_terminate: [ ... "/core/system-update-checker", ], }, }, }
Errors
Signaling mechanism
When routing is initiated by a requesting client by performing a
Directory.Open()
request on its namespace, or on
the
exposed directory
, it passes the server-end
handle of a zircon object that will provide the capability once routed. Errors
will result in that handle's object being closed with an epitaph. The epitaph
payload is always a Zircon status code.
Since routing is lazy and asynchronous, this message may arrive at any time after the routing operation is initiated.
NOTE: once routing has succeeded, the serving component can also close the same object with a status code of their choice. It is impossible for a client to discern if the object was closed by component manager or by the serving component, or another party delegated to thereafter.
The same error signaling mechanism is used for libc
-like calls like open()
.
See the section Troubleshooting for practical examples.
Error status codes
The following error codes may be sent by component manager to indicate a failed routing operation:
ZX_ERR_NOT_FOUND
: the capability was unable to be routed for one of the following reasons:- An optional capability was not provided in this configuration.
- A configuration error in any one of the components along the route path.
- The serving component is incompatible with this version of Fuchsia.
- A bug in the program of the serving component.
- A bug in any of the Resolvers or Runners involved in completing the routing operation.
ZX_ERR_ACCESS_DENIED
: the capability was unable to be routed because the requesting component is not allowed to access it. For example:- A policy allow-list for the capability exists but does not include the requesting component.
- The requesting component asked for rights greater than what was provided to it (ie, asking for read/write on a directory provided as read-only).
ZX_ERR_TIMED_OUT
: one of the routing steps timed out.ZX_ERR_INTERNAL
: component manager itself encountered an unexpected error, indicating a bug in the platform.
NOT_FOUND
, ACCESS_DENIED
, and INTERNAL
errors will reproduce for the same
capability so long as no software on the platform is updated. A software update,
even for a single component, can change a capability's route and might affect
the availability of that capability.
Principles of routing error semantics
- Minimality: since the error signaling path is shared between component manager and the serving component, component manager leaves the majority of the error space for the serving component to utilize.
- Client perspective: while routing depends on many individual
sub-operations that can each fail for a variety of reasons including errors
on the part of other component authors, the error semantics are tailored to
the requesting client and the needs of the requesting client. For example,
user-error on the part of an intermediate component author will still return
NOT_FOUND
for the requesting client.
Troubleshooting
This section contains common issues you may encounter trying to use
and
connect to capabilities from your component with suggested solutions.
Capability routing failed
Component manager performs capability routing to find the
source of a given capability once your component attempts to access the
capability. Routing can fail if one of the component manifests in the routing
path is configured incorrectly. For example, an offer
or expose
declaration
is missing from some component in the path, or one of the components in the
chain could not be resolved.
Do the following to check if a routing failure was the cause of channel closure:
Use
ffx component route
to check the routing for your component. This can either be used with the moniker of your component or your component's URL. For example:# check with the moniker ffx component route /core/ffx-laboratory:echo_realm/echo_client # check with a partial URL ffx component route echo_client.cm
Check the component logs with
ffx log
for a message beginning withFailed to route
that explains where the routing chain failed. For example:[echo_client][][W] protocol `fidl.examples.routing.echo.Echo` was not available for target `/core/ffx-laboratory:echo_realm/echo_client`: `fidl.examples.routing.echo.Echo` was not offered to `/core/ffx-laboratory:echo_realm/echo_client` by parent For more, run `ffx component doctor /core/ffx-laboratory:echo_realm/echo_client`
Check for an epitaph on the closed channel. The epitaph set for the most common routing failures is
ZX_ERR_NOT_FOUND
:[echo_client][][I] Connecting to Echo protocol failed with error "A FIDL client's channel to the service fidl.examples.routing.echo.Echo was closed: NOT_FOUND"
See routing errors for more.
For a self-contained example of failed capability routing, see
//examples/components/routing_failed
.
Receiving errors from routing
When capability routing fails, the underlying FIDL channel closes. FIDL protocol bindings return an error status if the channel was closed. Consider the following example:
Rust
let echo = connect_to_protocol::<EchoMarker>()
.context("Failed to connect to echo service")?;
let res = echo.echo_string(Some("Hippos rule!")).await;
match res {
Ok(_) => { info!("Call succeeded!"); }
Err(fidl::Error::ClientChannelClosed { status, service_name } => {
error!("Channel to service {} was closed with status: {}", service_name, status);
}
Err(e) => {
error!("Unexpected error: {}", e);
}
};
C++
fuchsia::examples::EchoPtr echo_proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(echo_proxy.NewRequest());
// Sets an error handler that will be called if an error causes the underlying
// channel to be closed.
echo_proxy.set_error_handler([&loop](zx_status_t status) {
printf("Channel was closed with status: %d\n", status);
// ...
});
echo_proxy->EchoString("Hippos rule!", [&](std::string response) {
// ...
});
To determine the underlying cause of a channel closure, you can inspect the optional epitaph set on the channel. To retrieve the epitaph on a closed channel, do the following:
Rust
let stream = echo.take_event_stream();
match stream.next().await {
Some(Err(fidl::Error::ClientChannelClosed { status, .. })) => {
info!("Channel was closed with epitaph: {}", status);
}
Some(m) => {
info!("Received message other than epitaph or peer closed: {:?}", m);
}
None => {
info!("Component failed to start or channel was closed by server");
}
}
C++
echo_proxy.set_error_handler([&loop](zx_status_t status) {
// If an Epitaph was present on the channel, its error value will be passed as
// the parameter.
printf("Channel was closed with epitaph: %d\n", status);
});
Component failed to start
You may encounter an error if capability routing was successful, but an issue occurred resolving or starting the component. The form of the error message depends on the component runner:
For the ELF runner, check the component manager logs with
ffx log --filter component_manager
. Look for a message starting withFailed to start component
. For example:[component_manager] WARN: Failed to start component `fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm`: unable to load component with url "fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm": error loading executable: "reading object at \"bin/routing_failed_echo_server_oops\" failed: A FIDL client's channel to the service fuchsia.io.File was closed: PEER_CLOSED"
For other runners, check the logs of the runner component. You can do this by running the following command:
ffx log --tags runner-name
To address the issue, verify the following:
- The
program
declaration in your component manifest is configured properly. For example, verify that the binary's path is spelled correctly. - The binary itself and all other resource needed to start the component are included in the package.
For an example of a component that failed to start due to a misconfigured
component manifest, see
//examples/components/routing_failed
.
Component terminated or closed the channel
If you have verified that routing succeeded and the component started successfully, you may be experiencing an issue where the source component closed the channel itself. This can happen while the component was running, or can be a side effect of the component terminating.
If the component terminated because it crashed, you can look for a crash report
in ffx log
that contains the component name in the dump:
[33860.645][klog][klog][I] crashsvc: exception received, processing
[33860.645][klog][klog][I] <== fatal : process echo_client.cm[21090] thread initial-thread[21092]
<stack trace follows...>
If the source component closed the channel itself, here are some tips to further troubleshoot the cause:
- Refer to the source component's logs for error messages.
- Use
ffx debug fidl
to examine the FIDL connection traffic withfidlcat
for errors or unexpected behavior.