RFC-0101:具有编号句柄的动态组件

RFC-0101:具有编号句柄的动态组件
状态已接受
区域
  • 组件框架
说明

提供了一种将编号的句柄传递给动态组件的方法。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-05-18
审核日期(年-月-日)2021-06-02

摘要

本文档建议向 fuchsia.sys2.Realm/CreateChild 添加一个新参数。此参数将包含一组编号的句柄。当创建的组件的 runner 被要求运行该组件时,它将收到这些句柄。所提供的句柄仅适用于在具有新持久性类型 single-run 的集合中运行的组件。如果集合中的组件的 durabilitysingle-run,则会在创建时启动,并在停止时销毁。这会将所提供的句柄限定为组件的单次运行。

设计初衷

Starnix 是一种 Component Framework v2 运行程序,可在 Fuchsia 上运行 Linux 二进制文件。Starnix 实现 Linux 系统接口,以便在不修改的情况下运行这些二进制文件。

Starnix 提供了一个 ffx 插件,可让用户从宿主命令行运行 Linux 组件。Starnix 希望将 Linux 组件的 stdin/stdout/stderr 连接到一组套接字句柄。此组件不通过公开 FIDL 协议与系统互动。而是通过提供的套接字与用户互动。

设计

背景

组件框架中的生命周期转换有两个维度。 第一个维度与存在性(CreatedDestroyed)有关。第二个维度与组件的执行(StartedStopped)有关。在只有进程而没有组件的其他操作系统中,这两个维度是等效的:创建进程与启动进程相同,进程在停止运行时会被销毁。

在为组件提供实参时,CreatedStarted 之间的区别非常重要。通常,单个组件实例可以多次启动和停止,因此组件管理器必须存储参数,以便在每次组件运行时提供这些参数。如果实参是句柄,这可能会有问题,因为并非所有句柄都可以复制。因此,任何不可复制的句柄都会被组件的单次运行“消耗”。

协议更新

背景中所述,创建组件与启动组件是不同的。因此,提供给 CreateChild 的句柄必须是以下任一类型:

  1. 每次启动组件时都可用。
  2. 作用域限定为组件的单次运行。

由于并非所有句柄都可以重复使用(存储在组件管理器中以供后续运行),因此只有在每次运行时都从其来源获取新句柄的情况下,才有可能实现 (1)。如需了解为何未选择在启动时提取句柄的方法,请参阅路由句柄。幸运的是,(2) 是一个可行的解决方案,可满足相关使用情形的需求。

系统会创建新表 ChildArgs,并将其作为参数添加到 Realm/CreateChild 中:

protocol Realm {
  /// If args contains numbered_handles, the collection must have a durability
  /// of type `single-run`.
  CreateChild(CollectionRef collection, ChildDecl decl, ChildArgs args)
      -> () error fuchsia.component.Error;
}

resource table ChildArgs {
  /// The numbered handles for the component instance.
  ///
  /// Only PA_FD and PA_USER* handles are valid arguments, and inclusion of any other
  /// handles will result in an error.
  1: vector<fuchsia.process.HandleInfo>:N numbered_handles;
}

此外,fuchsia.component.runner.ComponentStartInfo 表格将更新为包含带编号的句柄:

resource table ComponentStartInfo {
  6: vector<fuchsia.process.HandleInfo>:N numbered_handles;
}

运行程序会将这些句柄提供给组件,或者在运行程序不支持向组件提供编号句柄的情况下关闭这些句柄并返回错误。

集合持久性

通过 Realm 协议创建的组件位于集合中。 集合具有 durability 注释,用于指示集合中组件的生命周期语义。

将添加新的集合 durabilitysingle-run,以指示集合中的组件在创建时立即启动,并在停止时销毁。ChildArgs.numbered_handles 只能与标记为 single-run 的集合搭配使用。这会将 ChildArgs 中的实参限定为组件的单次运行。

collections: [
    {
         name: "playground",
         durability: "single-run",
    }
],

实现

更新组件管理器,以存储 ChildArgs,直到它们传递给运行程序,并处理 single-run 集合语义。

向后兼容性

此更改在 runner 方面是向后兼容的:runner 不需要使用提供的编号句柄。如果 runner 不支持编号句柄,则应关闭句柄。

此更改不向后兼容 Realm 客户端。

就 CML 而言,此变更向后兼容:唯一的变化是添加了一个持久性枚举。

性能

由于句柄直接提供给组件管理器,因此预计不会对性能产生影响。

安全注意事项

此变更引入了一种供父级向子级传递任意编号句柄的方法。这些句柄的交换由组件管理器和 runner 共同协调。只有在标记为 single-run 的集合中运行的组件才能以这种方式接收句柄。

测试

将扩展 CreateChild 的现有测试,以涵盖新的实参。

缺点、替代方案和未知因素

缺点

与下列替代方案相比,所提出的解决方案存在以下缺点:

  • 这是一种新的启动组件的方式:组件的生命周期不受功能绑定的影响。
  • 提供的句柄对组件框架的静态 CML 分析是不透明的。
  • 由于接收句柄的组件在停止时会被销毁,因此它们的持久性存储也会被清除。因此,使用持久性存储的组件不适合使用此功能。

将路由编号句柄作为功能

路由编号句柄可以由组件框架显式完成。

有多种不同的方法可以实现此目的,但它们都具有以下“形状”。

引入一个将由带编号的句柄的来源实现的协议:

protocol HandleProvider {
    Get(string handle_name) -> (fuchsia.process.HandleInfo handle);
}

然后,此协议会转换为功能:

capabilities: [
    {
        handle_provider: "stdin",
        path: "/svc/fuchsia.component.HandleProvider",
    },
],
expose: [
    {
        handle_provider: "stdin",
        from: "self",
    },
],

然后,可以通过 CML 将其路由到目的地,就像其他功能一样。

优势

  • 可能会发展为支持有关句柄的更多类型信息。
  • 使句柄的路由对组件框架显式可见。

缺点

在提取句柄之前,组件将无法启动。即使可以通过在组件管理器中缓存句柄来分摊性能影响,但激励性使用情形也无法从中受益,因为每次运行组件时句柄都会不同。在提议的设计中,宿主代码可以“发出即忘”启动组件的请求,并继续执行,就好像调用成功一样。在这种替代方案中,主机代码需要等待关联的句柄请求返回。

无法始终通过静态路由访问句柄提供程序。在激励性用例中,句柄提供程序位于宿主机上,通过 Overnet 连接到 Fuchsia 设备。这可以通过尽可能地进行路由,然后让“边缘”组件通过临时机制获取句柄,再将句柄返回给组件框架来解决。这会给想要使用该功能的开发者带来额外的负担。

提供程序无法区分来自给定功能路由的句柄请求。具体而言,请考虑以下使用情形:

  1. 用户从主机中的不同终端启动两个 Linux 组件。
  2. 组件在集合中实例化。
  3. 句柄提供程序收到两个针对同一句柄的请求。

此时,句柄提供程序无法知道哪个组件与哪个句柄请求相关联。虽然可以通过引入额外的客户端识别机制来解决此问题,但与建议的设计相比,这要复杂得多。

此解决方案比建议的设计更复杂,需要投入更多资源。 不过,所提出的设计不会阻止或冲突未来对编号句柄进行显式路由。

StartChild

此替代方案建议将带编号的句柄作为实参添加到对 RealmStartChild 的调用中。这与建议的设计类似,但缺点是会在组件绑定到组件提供的功能(这会启动组件)与 StartChild 调用之间引入竞态条件。具体来说,只有当 StartChild 调用赢得竞争时,组件才会收到编号的句柄,因为如果组件已启动,则不清楚句柄将如何传递。

初始协议

传递编号句柄可以通过 Starter 协议完成。启动器协议可用于启动组件,由组件管理器代表组件实现,并且可以像任何其他协议一样进行路由(即,组件可以由不是其父组件的组件启动)。

此协议可以像任何其他协议一样进行路由,因此客户端可以使用它来启动位于组件层次结构中任意位置的组件。

启动器协议包含一个接受编号句柄作为实参的方法:

[Discoverable]
protocol StarterWithArgs {
    /// Start the component that is bound to this protocol.
    /// If the component is already running, the call returns an error.
    Start(StartArgs start_args) -> () error fuchsia.component.Error;
};

此提案与 StartChild 非常相似。以这种方式公开的协议具有易于审核和列入许可名单的优势,但也会在调用 Start 和绑定到组件公开的任何其他功能之间引入竞态条件。此外,由于客户端不一定是父级,因此不一定能重启组件来提供实参,这会加剧排序问题。可以通过向协议添加停止方法来解决此问题。

客户端还需要在启动器协议和 Realm 协议之间协调子管理,而不是仅通过 Realm 协议管理子。