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

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

提供了一种向动态组件传递编号手柄的方法。

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

摘要

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

设计初衷

Starnix 是一个组件框架 v2 runner,用于在 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 集合语义。

向后兼容性

此更改对运行程序是向后兼容的:运行程序无需使用提供的编号手柄。如果运行程序不支持编号的句柄,则应关闭句柄。

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

此更改与 CML 向后兼容:唯一的更改是添加了耐久性枚举。

性能

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

安全注意事项

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

测试

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

缺点、替代方案和未知情况

缺点

与下列替代方案相比,所提解决方案有两个缺点:

  • 这是启动组件的新方法:组件的生命周期不受 capability 绑定的任何影响。
  • 提供的句柄对组件框架的静态 CML 分析而言是不透明的。
  • 由于接收句柄的组件在停止时会被销毁,因此其永久存储空间也会被清除。因此,使用永久存储空间的组件不适合使用此功能。

将编号的句柄路由为功能

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

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

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

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

然后,此协议会转换为 capability:

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

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

优势

  • 可以演变为支持有关句柄的更多类型信息。
  • 将手柄的路由明确显示给组件框架。

缺点

只有在提取句柄后,组件才能启动。即使可以通过在组件管理器中缓存句柄来摊销性能影响,但由于每次运行组件时句柄都会不同,因此该应用场景不会受益于此。在所提议的设计中,主机代码可以“触发并忘记”启动组件的请求,并继续执行,就像调用成功一样。在这种替代方案中,主机代码需要等待关联的句柄请求返回。

并非始终可以通过静态路由访问句柄提供程序。在启发用例中,句柄提供程序位于宿主机上,通过 Overnet 连接到 Fuchsia 设备。可以通过“尽可能”路由来解决此问题,然后让“边缘”组件通过临时机制提取句柄,然后将其返回给组件框架。这对想要使用该功能的开发者来说是额外的负担。

句柄提供程序无法区分来自给定 capability 路由的句柄请求。具体而言,请考虑以下动机用例:

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

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

与提议的设计相比,此解决方案的承诺更大,也更复杂。不过,所提议的设计不会阻止或与日后对编号手柄的显式路由冲突。

StartChild

此替代方案建议将编号的句柄作为参数添加到对 RealmStartChild 调用中。这与建议的设计类似,但缺点是会在组件绑定到组件提供的 capability(这会启动组件)和进行的 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 协议管理子级。