组件通常不会始终运行。大多数组件都是以 是异步的,这意味着它们通常会等待下一个 FIDL 消息 到达目的地。不过,这些组件会占用内存。本指南旨在 调整组件,使其自动停止,并在出现以下情况时释放资源 空闲。
概览
预期结果:
您需要对组件代码进行一些更改,以便它能够决定 停止。组件会在之前保留其状态和句柄 停止。保留此数据的过程称为“托管”。
组件的客户端不会知道组件已停止。 以这种方式停止组件不会中断它们与应用的 FIDL 连接, 组件。
Fuchsia 提供了相关库,可让您监控 FIDL 连接和 传出目录连接变为空闲状态,并重新打开这些连接 处理这些事件。
组件框架为组件提供用于存储标识名和数据的 API 并在下次执行时检索它们,通常是在句柄 读取时或发出新的功能请求时。我们将在下一个视频中 。
Fuchsia 快照和 Cobalt 信息中心将包含有用的生命周期指标。
哪些组件是合适的候选组件?
我们建议您查看具有以下特征的组件:
流量过于刺耳。该组件可以启动并处理这些流量,然后 停止运行启动和更新路径中包含大量组件 只是在这些时段需要的,而其他时候则是在浪费时间 RAM,例如
core/system-update/system-updater
。没有过于有状态。您可以在组件停止之前保留状态。在 我们可以编写代码来保留所有重要状态。在实践中,我们 您需要在节省的内存和持久 必要的状态
内存用量高。使用
ffx profile memory
查看组件的内存用量。例如,它显示了典型的console-launcher.cm
系统使用732 KiB
的私有内存。专用内存仅为内存 因此我们保证至少释放出该组件的可用内存 内存占用请参阅 测量内存用量。Process name: console-launcher.cm Process koid: 2222 Private: 732 KiB PSS: 1.26 MiB (Proportional Set Size) Total: 3.07 MiB (Private + Shared unscaled)
http-client.cm
是不包含
状态,仅用于指标和崩溃
上传。因此,我们对其进行了调整,使其在经过相应配置后在空闲时停止。
已知限制
检查:如果组件通过检查功能发布诊断信息, 当组件停止运行时,这些信息就会被舍弃。 https://fxbug.dev/339076913 跟踪保留 检查数据。
挂起获取:如果您的组件是挂起获取的服务器或客户端 FIDL 方法,则很难保持这种联系,因为 FIDL 绑定无法保存和恢复 正在进行的通话。您可以将该 FIDL 方法转换为事件,然后将 单向确认
目录:如果您的组件提供目录协议,该组件将 很难保持这种连接,因为 由 VFS 库提供VFS 库目前尚未公开返回 底层通道和相关状态(例如定位指针)。
所有这些都有充分的理由。您可以联系 与您的应用场景联系
检测空闲性
停止空闲组件的第一步是增强该组件的代码 了解设备是否已进入空闲状态,这意味着:
FIDL 连接处于空闲状态:组件通常会声明多个 FIDL 协议功能之后,客户端就会连接到这些协议, 需要它。这些连接不应具有需要 组件的注意力。
传出目录处于空闲状态:组件提供用于 发布其传出功能该网页上不应存在 表示对此组件的功能请求, 到传出目录(除了由外部 IP 地址建立的
component_manager
。其他后台业务逻辑:例如,如果某个组件发出 在后台进行网络请求以响应 FIDL 方法时, 否则就会将该组件视为空闲状态。 该组件在请求过程中停止操作可能是不安全的。
我们都有可用于检测每种情况下的空闲情况的 Rust 库。 https://fxbug.dev/332342122 跟踪相同的 功能。
检测空闲 FIDL 连接
您可以使用 detect_stall::until_stalled
转换 Rust
FIDL 请求流转换为 FIDL 端点,如果
在指定的超时时间内处于空闲状态。您需要将组件添加到
位于 src/lib/detect-stall/BUILD.gn
的可见性列表。参阅 API 文档
和测试详细信息。http-client.cm
的使用方式如下:
async fn loader_server(
stream: net_http::LoaderRequestStream,
idle_timeout: fasync::Duration,
) -> Result<(), anyhow::Error> {
// Transforms `stream` into another stream yielding the same messages,
// but may complete prematurely when idle.
let (stream, unbind_if_stalled) = detect_stall::until_stalled(stream, idle_timeout);
// Handle the `stream` as per normal.
stream.for_each_concurrent(None, |message| {
// Match on `message`...
}).await?;
// The `unbind_if_stalled` future will resolve if the stream was idle
// for `idle_timeout` or if the stream finished. If the stream was idle,
// it will resolve with the unbound server endpoint.
//
// If the connection did not close or receive new messages within the
// timeout, send it over to component manager to wait for it on our behalf.
if let Ok(Some(server_end)) = unbind_if_stalled.await {
// Escrow the `server_end`...
}
}
检测空闲的传出目录
您可以使用
fuchsia_component::server::ServiceFs::until_stalled
方法
将 ServiceFs
转换为解除绑定传出目录服务器的绑定。
端点自动启动。参阅 API
文档和测试。http-client.cm
的使用方式如下:
#[fuchsia::main]
pub async fn main() -> Result<(), anyhow::Error> {
// Initialize a `ServiceFs` and add services as per normal.
let mut fs = ServiceFs::new();
let _: &mut ServiceFsDir<'_, _> = fs
.take_and_serve_directory_handle()?
.dir("svc")
.add_fidl_service(HttpServices::Loader);
// Chain `.until_stalled()` before calling `.for_each_concurrent()`.
// This wraps each item in the `ServiceFs` stream into an enum of either
// a capability request, or an `Item::Stalled` message containing the
// outgoing directory server endpoint if the filesystem became idle.
fs.until_stalled(idle_timeout)
.for_each_concurrent(None, |item| async {
match item {
Item::Request(services, _active_guard) => {
let HttpServices::Loader(stream) = services;
loader_server(stream, idle_timeout).await;
}
Item::Stalled(outgoing_directory) => {
// Escrow the `outgoing_directory`...
}
}
})
.await;
}
等待其他后台业务逻辑
ServiceFs
在生成
Item::Stalled
条消息。如果你有一些背景信息,这可能会有问题
阻止组件停止运行,但 ServiceFs
已变为
同时空闲,并且过早取消绑定传出目录
端点。为了处理这些情况,您可以禁止 ServiceFs
空闲状态ServiceFs
生成的 Item::Request
包含一个
ActiveGuard
。只要主动守卫在范围内,
ServiceFs
不会变为空闲状态,并且会继续产生功能请求,因为
都会出现这种问题
同样,您可以创建 ExecutionScope
来生成
与处理 FIDL 连接相关的后台工作,以及
ExecutionScope::wait()
,等待这些任务完成。例如,
在此之前,http-client.cm
中的 loader_server
函数不会返回
这反过来又会使得active_guard
Item::Request
在范围内,阻止 ServiceFs
停止。
由框架托管句柄和状态
当连接处于空闲状态且库为您提供了未绑定的服务器时 下一步是托管这些标识名,也就是说,将其发送到 组件框架的安全性
无状态协议
某些 FIDL 连接不具有状态。每个请求的运作方式都完全相同 无论是通过同一连接发送,还是通过不同连接发送。 对于此类连接,您可以按以下步骤操作:
如果尚未在组件清单中声明该功能,请进行声明。您可能需要 声明相应功能(如果此协议连接派生自另一个 连接,并且通常不会从传出目录提供。
在声明 capability 时添加了
delivery: "on_readable"
。您需要添加 将你的组件添加到delivery_type
可见性列表中tools/cmc/build/restricted_features/BUILD.gn
。框架 然后,系统会监控 新的连接请求,并将服务器端点连接到提供商 组件。示例:capabilities: [ { protocol: "fuchsia.net.http.Loader", delivery: "on_readable", }, ],
为该功能添加来自
self
的使用声明,以便程序可以 从其传入的命名空间连接到该实例您可以在以下位置安装该功能/escrow
目录,以便将其与您的 组件。示例:{ protocol: "fuchsia.net.http.Loader", from: "self", path: "/escrow/fuchsia.net.http.Loader", },
从传入的命名空间连接到 capability,传递未绑定的 来自
detect_stalled::until_stalled
的服务器端点。if let Ok(Some(server_end)) = unbind_if_stalled.await { // This will open `/escrow/fuchsia.net.http.Loader` and pass the server // endpoint obtained from the idle FIDL connection. fuchsia_component::client::connect_channel_to_protocol_at::<net_http::LoaderMarker>( server_end.into(), "/escrow", )?; }
总之,这意味着组件框架将监控空闲连接 然后在需要再次读取时 情况。如果组件已停止,则会启动组件。
外发目录
我们必须使用其他 API 来托管主传出目录连接
(即 Item::Stalled
中 ServiceFs
返回的这个地址),因为服务器
端点是一个入口点,所有其他连接均可从该入口点
组件。对于 ELF 组件,您可以将传出目录发送到
框架通过 fuchsia.process.lifecycle/Lifecycle.OnEscrow
FIDL 事件进行通信:
将
lifecycle: { stop_event: "notify" }
添加到组件.cml
:program: { runner: "elf", binary: "bin/http_client", lifecycle: { stop_event: "notify" }, },
获取生命周期编号句柄,将其转换为 FIDL 请求流,然后 使用
send_on_escrow
发送事件:let lifecycle = fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::Lifecycle, 0)).unwrap(); let lifecycle: zx::Channel = lifecycle.into(); let lifecycle: ServerEnd<flifecycle::LifecycleMarker> = lifecycle.into(); let (mut lifecycle_request_stream, lifecycle_control_handle) = lifecycle.into_stream_and_control_handle().unwrap(); // Later, when `ServiceFs` has stalled and we have an `outgoing_dir`. let outgoing_dir = Some(outgoing_dir); lifecycle_control_handle .send_on_escrow(flifecycle::LifecycleOnEscrowRequest { outgoing_dir, ..Default::default() }) .unwrap();
组件发送
OnEscrow
事件后,便无法 监控更多功能请求。因此,此按钮之后应立即退出。 下次执行时,您的组件将会返回其启动信息,outgoing_dir
句柄。如需了解所有这些组件的组成方式,请参阅
http-client
。
有状态协议和其他重要状态
fuchsia.process.lifecycle/Lifecycle.OnEscrow
事件接受另一个参数,
一个 escrowed_dictionary client_end:fuchsia.component.sandbox.Dictionary
,
是对 Dictionary
对象的引用。字典
键值对映射。
您可以使用
fuchsia.component.sandbox.Factory
创建Dictionary
,并在Factory
协议上调用CreateDictionary
:use: [ { protocol: "fuchsia.component.sandbox.Factory", from: "framework", } ]
let factory = fuchsia_component::client::connect_to_protocol::< fidl_fuchsia_component_sandbox::FactoryMarker >().unwrap(); let dictionary = factory.create_dictionary().await?;
您可以通过调用
Dictionary
针对Dictionary
FIDL 连接的Insert
。请参阅fuchsia.component.sandbox
FIDL 库文档,适用于 其他方法:let bytes = vec![...]; let data = fidl_fuchsia_component_sandbox::Data::Bytes(bytes); let dictionary = dictionary.into_proxy().unwrap(); dictionary .insert( "my_data", fidl_fuchsia_component_sandbox::Capability::Data(data) ) .await??;
在退出之前,请在
send_on_escrow
中发送Dictionary
客户端端点:lifecycle .control_handle() .send_on_escrow(flifecycle::LifecycleOnEscrowRequest { outgoing_dir: Some(outgoing_dir), escrowed_dictionary: Some(dictionary.into_channel().unwrap().into_zx_channel().into()), ..Default::default() })?;
下次启动时,您可以从启动句柄获取此字典:
if let Some(dictionary) = fuchsia_runtime::take_startup_handle( HandleInfo::new(HandleType::EscrowedDictionary, 0) ) { let dictionary = dictionary.into_proxy()?; let capability = dictionary.get("my_data").await??; match capability { fidl_fuchsia_component_sandbox::Capability::Data( fidl_fuchsia_component_sandbox::Data::Bytes(data) ) => { // Do something with the data... }, capability @ _ => warn!("unexpected {capability:?}"), } }
Dictionary
对象支持各种商品数据类型。如果您的
组件的状态小于 fuchsia.component.sandbox/MAX_DATA_LENGTH
,
您可以考虑存储 fuchsia.component.sandbox/Data
项,
用于存储字节矢量。
我想等待频道可供读取
在停止前,如果您想为组件框架安排
等到频道具有可读性,然后将该频道传回您的
组件,则可以使用相同的 delivery: "on_readable"
方法。这个
会泛化到组件未公开的 FIDL 协议,
。它甚至支持不采用 FIDL 协议的通道。如
举个例子,假设您的组件包含一个 Zircon 异常通道,并且需要
指示框架等待该通道可读取,然后启动
您的组件,您可以声明以下 .cml
:
capabilities: [
{
protocol: "exception_channel",
delivery: "on_readable",
path: "/escrow/exception_channel",
},
],
use: [
{
protocol: "exception_channel",
from: "self",
path: "/escrow/exception_channel",
}
]
请注意,未公开 exception_channel
功能。此功能
由组件本身使用该组件可能会打开 /escrow/exception_channel
从其传入的命名空间中,与要等待的通道相关联。这个频道
可读,则框架将在传出消息中打开 /escrow/exception_channel
目录,根据需要启动组件。总的来说,您可以声明
功能,并使用它们从 self
将句柄托管给 component_manager
。
如果您需要其他类型的代码,请与组件框架团队联系 触发器,例如等待自定义信号或等待计时器。
测试
我们建议您增强现有集成测试,
组件可以自行停止并重新启动,而不会中断 FIDL 连接。
如果您已有启动组件并向应用发送数据的集成测试,
FIDL 请求,您可以使用组件事件匹配器来验证
当没有消息时,您的组件将停止。请参阅
如需查看具体操作示例,请参阅 http-client
测试。
着陆和指标
如果您希望针对特定产品优化该组件 则可以向组件添加结构化配置,以控制 空闲超时时间。
组件框架记录组件在 并将这些数据上传到 Cobalt。您可以在此 dashboard,以便微调空闲超时。
获取反馈概况时,
字段,初始组件和最新组件执行的时间戳将是
可在选择器 <component_manager>:root/lifecycle/early
和
<component_manager>:root/lifecycle/late
。您可以将这些
事件和其他错误日志,以帮助调查错误是否由
而是由组件停止不当导致的