本文档演示了如何使用 capability 将组件连接在一起 以及供父级组件管理其子级的其他工具。
概念
在继续阅读本指南之前,您应该了解以下概念:
* 组件框架会组建
命名空间
针对
组件
组件声明
那个
描述
功能
该组件需要
函数。该组件向其他组件提供的功能会组合到
一
公开目录
,了解所有最新动态。
* 每个组件都会收到一个服务器端的句柄,
Directory
频道称为
传出目录
,了解所有最新动态。组件的程序
使用户可通过该目录检测到其提供的任何功能。
* 在运行时,
组件实例树
它描述了
组件实例
,了解所有最新动态。组件实例
树上的功能路由统称为
用作
组件拓扑
,了解所有最新动态。
* 父组件在其自身中以静态方式声明子组件。
组件清单
也可以使用
组件集合
,了解所有最新动态。集合是
用于存储可能在运行时创建和销毁的动态子项的容器
使用 fuchsia.component.Realm
框架协议。
通过路由连接功能
组件通过功能相互交互。提供的功能
您需要在该组件的清单中声明某个组件
可供其他人使用,通过 expose
和 offer
路由到父级/子组件
声明。使用该功能的其他组件也需要声明
在其清单中使用相应字段为了让其程序能够使用
在运行时,必须将所使用的功能路由到该组件,即
由家长提供或由孩子提供。
功能路由是指由组件执行的递归过程 通过跟踪各个路由,以标识服务组件, 请参阅清单文件说明的步骤在以下情况下,系统会启动功能路由:
- 组件的程序会在其 命名空间 ,了解所有最新动态。
- 组件的程序会在另一个组件的 公开目录 ,了解所有最新动态。
- 开发者调用
ffx component route
。 - 启动依赖于其备份的解析器或运行程序的组件 解析器或运行程序功能尚未路由。
路由是延迟执行,而功能可以配置为 由父级或子级提供(直接或通过进一步的 委托),则调用 路由操作。实际上,这意味着 除非 。
提供 capability 实现
提供功能的组件必须在其
使用 capabilities
声明的组件清单。
请参阅以下示例,其中声明了 FIDL 协议功能:
{
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",
},
],
}
在运行时,服务器组件通过 使用 fuchsia.io 在其传出目录中提供 协议。生成的 FIDL 绑定会封装此句柄并启用提供程序 要开始接收传入请求,请执行以下操作:
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++
```cpp // 传入服务请求的处理程序 class EchoImplementation : public fidl::examples::routing::echo::Echo { 公开: void EchoString(fidl::StringPtr value, EchoStringCallback callback) 替换 { 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(); // 初始化检查器 inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{}); inspector.Health().StartingUp(); // 提供 Echo 协议 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>处理程序 = [&](fidl::InterfaceRequest<fidl::examples::routing::echo::Echo> request) { binding.Bind(std::move(request)); }; context->outgoing()->AddPublicService(std::move(handler)); // 组件正在提供服务,并已准备好处理传入请求 inspector.Health().Ok(); returnLoop.Run(); } ```
连接到路由功能
客户端组件声明它们可能在其组件中请求的功能
包含 use
声明的清单。
请参阅以下示例,了解使用 FIDL 的客户端组件清单 协议:
{
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" },
],
}
在运行时,客户端组件使用 fuchsia.io 协议,以获取功能 由其他组件提供Fuchsia 组件库支持 生成的 FIDL 绑定,来提供通过 频道:
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;
}
组件的父级负责向组件路由所有必要资源, 功能。
将一些已使用的功能标记为可选
组件运行并非必须使用其使用的所有功能 成功。有时,如果存在 功能缺失,其存在只能启用一些 替代行为
使组件框架能够理解组件的哪些功能
以及哪些功能对于组件是可选的,请使用
availability
字段。
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",
},
]
如果组件具有 required
,请对 capability 使用声明(默认设置)
但其父项以 optional
的形式提供功能,则
静态功能分析器将产生错误,并且
则始终会失败。
使用可选功能
如果组件的父级对如下功能的 offer
设置了一项功能:
availability: "optional"
,则该功能在运行时可能不可用。
组件的
命名空间
会出现
该功能是否可用任何尝试打开以下路径的路径
该功能会导致向 Directory.Open()
提供句柄
以 ZX_ERR_NOT_FOUND
长叹号结束的通话。
使用 libc
方法(如 open()
或 stat()
)将返回 ENOENT
。
路由功能
组件只能访问传送给它们的功能。功能 可以源自组件拓扑中的任何位置,但前提是功能有效 以功能中的以下声明链形式存在 provider:
如需将功能提供程序与请求这些功能的组件连接起来, 执行以下操作:
向功能提供程序组件添加
offer
或expose
声明:{ 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", }, ], }
对于组件实例树中的每个中间组件,添加 额外添加
expose
和offer
声明,直至达到 包含use
声明的组件:{ // 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", ], }, ], }
可选依赖项
如果某个组件依赖于某项功能,则最多
该组件的父级来确定该组件是否接收该功能
。提供某项功能时,组件可以设置 availability
字段
更改为 optional
、required
或 same_as_target
。每个值都有
以下语义:
optional
:优惠的目标必须声明其处理 则缺少此功能,具体方法是将其use
声明标记为optional
。 如果目标无法执行此操作(也就是说,目标的可用性是required
),那么路由该功能将会导致 错误。required
:目标必须获得此功能。如果优惠来源为parent
且组件的父级(目标的祖父级)提供了此属性 作为可选功能,那么路由该功能将会导致错误 因为父级无法保证该功能的可用性。same_as_target
:此功能的可用性取决于 目标的期望。如果目标依赖于此 功能,那么此优惠也是可选的。如果目标的 依赖此 capability,则此优惠也将 必填字段。
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",
},
]
与 use 声明一样,availability
字段可以省略,其中
则默认为 required
。
过渡依赖项
通过组件框架,组件可以软过渡到使用和 提供可选和必需的功能。在过渡期 可用性标记,即使用功能的组件,不会导致审查 验证错误,无论父项是必需项、可选项还是与父项相同 目标。请注意,尽管此字段的存在是为了启用软过渡,组件 最终应确定是可选还是必需。
要使用此功能,子组件会将其可用性标记为 "transitional":
use: [
{
// It is ok if this protocol is offered as "required" or "optional"
protocol: "fuchsia.examples.Echo",
availability: "transitional",
},
]
管理子组件
组件可以从组件拓扑中的任何位置相互交互 但前提是它们之间存在有效的功能路由。 还有一些其他方法可让父组件 直接子女。
以下示例组件声明了一个名为 lifecycle
的静态子级
以及一个名为 echo
的集合,您可以在其中创建其他子组件
在运行时:
{
// ...
// 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",
"fuchsia.logger.LogSink",
],
from: "parent",
to: [
"#echo",
"#lifecycle",
],
},
],
}
请注意,集合的行为类似于父级中的静态子实例 您可以为其命名并提供特定的功能 。集合中的所有子组件均可访问这组功能 提供给它。
启动子组件
组件框架提供 fuchsia.component.Binder
父组件的协议,用于显式启动可能不会公开
任何其他功能由于此功能由框架提供,
子组件只需通过其组件清单公开该组件即可:
{
// ...
// 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",
},
],
}
创建动态子级
要在运行时创建新的子组件,请使用
fuchsia.component.Realm
协议,用于创建
现有集合调用 CreateChild
方法:
CollectionRef
:描述集合,其中 系统就会添加该组件Child
:组件声明,包括其名称和 组件网址。
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();
});
}
关联到子 capability
由于在构建时并不知道动态组件的父项, 无法在 组件清单。
如需连接动态子实例公开的 capability,请执行以下操作:
使用
fuchsia.component.Realm
协议打开孩子的 公开目录调用OpenExposedDir
方法(其中包含子组件的名称和集合名称):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); }); }
使用公开目录连接到子级公开的 capability 处理为根:
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(); }); }
销毁动态子级
当不再需要动态子级时,请使用
使用 fuchsia.component.Realm
协议销毁组件
实例。使用DestroyChild
ChildRef
,表示集合中的子项。
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();
});
}
如果组件当前正在运行,这会导致其停止运行。要处理此问题,请执行以下操作: 事件,请参阅监听停止事件。
控制组件生命周期
组件框架提供了用于修改各种组件并与之交互的功能 组件生命周期的各个部分。
如需详细了解生命周期概念,请参阅 组件生命周期。
生命周期通知
ELF 运行程序会使用
fuchsia.process.lifecycle.Lifecycle
协议。
如需在子组件中监听停止通知,请执行以下操作:
在组件中订阅生命周期事件 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" }, },
使用 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: fuchsia_zircon::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_; };
从父级开始
利用组件清单,您可以将子级标记为
eager
,这会使组件框架隐式
从父级开始
如果即时子级因任何原因(例如缺少组件)而无法启动, 组件管理器表现出以下行为:
- 如果父组件不是根组件,则父组件将开始,但 与其绑定的组件将观察到连接断开(就像 其他失败的绑定)。
如果父组件是根组件,组件管理器将会崩溃,并显示 错误消息,例如:
[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`
标记为 eager
的组件如果不存在,可能会导致系统崩溃
如果它们的祖先实体也被标记为 eager
(直到根组件)。这是
因为许多构建配置都会创建包含
一部分可用组件。为避免出现这种问题,请声明这些
组件使用核心领域分片来确保它们
已从测试 build 和产品映像中安全排除。
eager
组件也应位于同一软件包集中
因为此组件会与它的父项同时启动,
。通常,eager
组件应位于产品的基础软件包中
。
如需确定您的软件包是否在基础软件包集中,请运行以下命令 命令:
fx list-packages --verbose my-package
此命令会输出包含匹配软件包的软件包集的列表
找到。例如,system-update-checker
位于 base
和 universe
中
软件包集:
$ fx list-packages --verbose system-update-checker
system-update-checker [base universe]
您还可以使用 --base
查看基础软件包集中的所有软件包
选项:
fx list-packages --base
终止时重新启动
利用组件清单,您可以控制
使用 on_terminate
构建组件。具有
“reboot-on-terminate”如果存在以下情况,则系统将正常重新启动:
组件因任何原因终止(包括成功退出)。
如需启用此功能,请执行以下操作:
在父级的组件清单中将子级标记为
on_terminate: reboot
:// 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", }, ], }
将该组件的名称添加到组件管理器的安全政策许可名单中 在
//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", ], }, }, }
错误
信号机制
当发出请求的客户端通过执行
针对其命名空间的 Directory.Open()
请求,或针对
该
公开目录
,它会通过服务器端传递
zircon 对象的句柄,发送后会提供相应功能。错误
将导致该句柄的对象以书写形式关闭。墓碑
载荷始终是 Zircon 状态代码。
由于路由是延迟且异步的,因此此消息可能会随时到达 。
注意:一旦路由成功,服务组件还可以关闭 将对象替换为自己选择的状态代码。客户端无法做到这一点 来辨别对象是由组件管理器还是由 组件或其后委托给其他方。
类似 libc
的调用(例如 open()
)也使用相同的错误信号机制。
如需查看实际示例,请参阅问题排查部分。
错误状态代码
组件管理器可能会发送以下错误代码来指示 路由操作:
ZX_ERR_NOT_FOUND
:无法针对以下其中一项进行路由: 原因如下: <ph type="x-smartling-placeholder">ZX_ERR_ACCESS_DENIED
:无法路由功能,因为 不允许请求组件访问该组件。例如:- 该功能的政策许可名单已存在,但 不包含发出请求的组件。
- 发出请求的组件所请求的权限高于所提供的权限 (即,请求对以只读方式提供的目录进行读/写)。
ZX_ERR_TIMED_OUT
:其中一个路由步骤超时。ZX_ERR_INTERNAL
:组件管理器本身遇到了意外错误。 这表明平台存在 bug。
NOT_FOUND
、ACCESS_DENIED
和 INTERNAL
错误也可重现
但前提是平台上没有更新任何软件。软件更新
都会改变功能的路线,并可能影响
该功能的可用性
路由错误语义的原则
- 最小性:因为错误信号路径在组件之间共享 与服务组件相关联后,组件管理器会将大部分 供服务组件使用的错误空间。
- 客户端视角:路由取决于多个单个用户
子操作都可能因各种原因而失败,包括错误
而对于其他组件作者,错误语义是针对
发出请求的客户端和发出请求的客户端的需求例如:
中间组件作者的 user-error 仍会返回
NOT_FOUND
(适用于发出请求的客户端)。
问题排查
本部分包含您在尝试use
和
通过建议的解决方案连接到组件的功能。
功能路由失败
组件管理器会执行功能路由,以找到
指定功能的来源之后,您的组件尝试访问
功能。如果路由中的某个组件清单,则路由可能会失败
路径配置不正确。例如,offer
或 expose
声明
路径中的某个部分或
链无法解析。
执行以下操作,检查路由故障是否是导致通道关闭的原因:
使用
ffx component route
检查组件的路由。这个 可以与组件的名称或组件的 网址。例如:# 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
使用
ffx log
检查组件日志,查找以Failed to route
开头的消息(说明路由链出现故障的位置)。例如:[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`
检查已关闭频道上的墓记。墓碑 针对最常见的路由故障设置的值为
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"
如需了解详情,请参阅路由错误。
如需查看失败功能路由的独立示例,请参阅
//examples/components/routing_failed
。
收到路由错误
当功能路由失败时,底层 FIDL 通道会关闭。FIDL 协议 如果通道已关闭,绑定将返回错误状态。考虑使用 示例:
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) {
// ...
});
若要确定渠道关闭的根本原因,您可以查看 可选的 epitaph。要检索 如果是封闭式渠道,请执行以下操作:
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);
});
组件未能启动
如果功能路由成功,但存在一个问题,则可能会遇到错误 发生解析或启动组件的问题。错误消息的形式 依赖于组件运行程序:
对于 ELF 运行程序,请使用
ffx log --filter component_manager
检查组件管理器日志。查找以Failed to start component
开头的邮件。例如:[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"
对于其他运行程序,请查看运行程序组件的日志。您 为此,可以运行以下命令:
ffx log --tags runner-name
如需解决此问题,请验证以下内容:
有关因配置错误而无法启动的组件示例
组件清单,请参见
//examples/components/routing_failed
。
组件终止或关闭了频道
如果您已验证路由成功且组件已启动 则表示您可能会遇到来源组件关闭的问题 频道本身这可能发生在组件运行时,也可能是 组件终止的副作用
如果该组件因崩溃而终止,您可以查找崩溃报告
在包含转储中的组件名称的 ffx log
中:
[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...>
如果来源组件自行关闭了频道,以下提示可帮助您 排查问题: