RFC-0107:动态商品

RFC-0107:动态优惠
状态已接受
区域
  • 组件框架
说明

允许在 CFv2 中运行时创建优惠。

Gerrit 更改
作者
审核人
提交日期(年-月-日)2021-05-24
审核日期(年-月-日)2021 年 6 月 24 日

摘要

在组件框架 v2 (CFv2) 中,父组件可以使用集合在运行时向拓扑添加动态子组件。不过,无法在运行时配置向此类动态子组件提供的功能集。优惠是在父组件的清单中声明的,在父组件开始运行时,该清单是不可变的。

父组件的清单会统一处理给定集合中的所有动态子组件;它甚至无法提及集合中的单个组件。这意味着存在以下限制:

  1. 无法向单个动态子级提供功能。 向集合提供功能,即向该集合中的所有动态子项提供功能。
  2. 无法将单个动态子项的功能提供给其他组件。从集合中提供功能会创建一个聚合 FIDL 服务,该服务允许连接到相应集合中的任何动态子项。

本文档提出了动态优惠,可在运行时将其添加到组件拓扑中。

利益相关方

  • 在运行时构建动态拓扑的 CFv2 用户。他们将由 Driver Framework 团队代表。
  • 组件框架区域的所有者,他们可以验证设计是否合理,并且不会与任何未来计划相冲突。
  • 安全性,可确保此功能不会损害系统的安全属性。

设计初衷

大多数 CFv2 使用场景都涉及组件的静态拓扑,这些组件协同工作以完成某些任务。所有这些组件的身份和关系都是预先已知的,并且可以硬编码到组件清单中。

在其他一些使用情形中,开发者不知道将要运行的确切组件集,但知道这些动态组件将如何相互关联。开发者可以将所有动态组件分组到集合中,其中给定集合中的每个组件都提供相同的能力集。

不过,有些 CFv2 用户希望在运行时构建复杂的拓扑,为每个动态子项提供一组独特的功能,并在动态子项之间提供路由功能。

用例:将驱动程序作为组件

在驱动程序框架的“将驱动程序作为组件”项目中,动态实例化的驱动程序组件依赖于其他动态实例化的驱动程序组件提供的功能。我们将从一个驱动程序组件提供给另一个驱动程序组件的功能称为驱动程序间功能

CFv2 本身不支持驱动程序间功能。驱动程序框架团队通过在 driver_manager 本身中实现类似功能路由的功能来解决此问题。此问题解决方法存在一些明显的缺点,并且会暴露驱动程序间功能与“正常”功能之间令人困惑的差异:

  • 组件通过在其传入命名空间中打开路径来连接到“正常”功能,但通过驱动程序框架特定的 exposed_dir 字段连接到驱动程序间功能。
  • 获得 exposed_dir 后,接收组件可以访问其依赖项实现的所有协议driver_manager 不会限制接收方对所需协议的访问权限。驱动程序框架团队可以修复此问题,但这样做会进一步复制组件框架机制。
  • 驱动程序组件需要在其清单的 use 部分中声明所依赖的“常规”功能,但不应以相同方式列出驱动程序间功能。如果它们是,组件管理器将无法找到满足 useoffer
    • 目前,组件管理器大多会忽略这些多余的 use,但这可能会在短期内造成混淆,从长远来看,多余的 use 可能会变成错误。

驱动程序作者不应将“正常”功能与驱动程序间功能视为不同,因此应尽快移除这种区别。等待时间越长,模型更改时需要迁移的驱动程序就越多。

设计

此提案扩展了 fuchsia.sys2.Realm/CreateChild API,使其能够接受 OfferDecl 的列表,用于描述将向新创建的子级提供的额外功能(超出通常会提供的功能)。我们将以这种方式指定的 OfferDecl 称为动态提供。以正常方式(即在组件清单中列出)指定的 OfferDecl 将称为静态提供

任何类型的广告资源(即协议、目录等)都可以用作动态广告资源。

动态优惠始终定位到新创建的子组件。因此,在创建组件时,系统会确定定位到该组件的一组动态优惠,此后这组优惠便不可变。(OfferDecltarget 字段是否必须实际指定将在 API 审核中确定。)

适用于静态优惠的任何来源也适用于动态优惠。此外,与静态优惠不同,动态优惠可以使用“同级”动态子组件(即与目标具有相同父级的组件)作为来源。如果动态优惠的来源是动态子项,则没有目标必须与来源位于同一集合中的限制。

创建子组件后,动态优惠的行为应与静态优惠完全相同,包括组件关闭顺序等注意事项。

销毁

当动态优惠的来源或目标被销毁时,该动态优惠也会被销毁。在销毁优惠后,系统将表现得好像该优惠从未存在过一样。

如果可能,动态优惠的用户应优先销毁目标,然后再销毁来源。如果被销毁的是目标,那么从拓扑中移除相应出价的过程非常简单(如果出价落入森林中,周围没有任何组件可以听到它...)。不过,有时这种情况无法避免,源会先于目标被销毁。值得注意的是,使用单次运行集合(在 RFC-0101 中引入)时,动态组件会在终止后立即销毁,而终止可能随时发生。

即使是正在销毁的来源,该过程也很简单:

  1. 组件管理器关闭源组件。这样可防止目标打开与源的任何新连接,并指示运行程序停止源组件(如果该组件正在运行)。
  2. 当源组件停止时,源和目标之间的任何通道都将关闭。
  3. 来源组件和动态优惠将同时从拓扑中移除。

从目标的角度来看,与功能对应的渠道会关闭,并且任何重新获取功能的尝试都会永久失败,就好像该功能一开始就没有路由一样。

此 RFC 未提供重新创建此类优惠的机制。

防止出现依赖环

CFv2 要求组件及其之间的强提供形成有向无环图,以便组件可以按依赖顺序正常关闭。对于静态优惠,我们可以通过查看组件清单来确保没有依赖关系循环。

对于动态优惠,以下规则足以确保无环依赖关系图:

  1. 动态组件创建顺序是严格的全序。换句话说,不会同时向拓扑添加两个动态组件。
  2. 动态优惠始终与其目标组件同时创建。
  3. 所有优惠(包括静态优惠和动态优惠)均不可变。
  4. 所有优惠(包括静态优惠和动态优惠)必须始终具有有效的来源和有效的目标。CreateChild 会在动态创建优惠时检查此属性,并且每当来源或目标被销毁时,优惠也会被销毁。

总而言之,这意味着动态优惠的来源始终比其目标更旧。如果来源较新,则表示在创建目标时来源不存在,因此优惠在创建时会具有无效的来源。由于“创建顺序”是严格的全序,因此不可能出现依赖关系循环。

处理无效路线

截至撰写本文时,Component Manager 不会主动拒绝具有无效路由配置的组件清单。也就是说,如果某个来源提供的功能未公开,或者某个组件未收到其所用功能的提供,则在创建组件时不会引发任何错误。仅当组件尝试打开错误路由的功能时,才会引发错误。

秉持“动态优惠应与静态优惠类似”的原则,我们在此处实现相同的行为。组件可能会为来源未公开的功能创建动态提供。同样,父组件可以创建动态子组件,而无需为它使用的每项功能提供 offer。

未来,如果 Component Manager 更改为在这些情况下更早地返回错误,我们也会更改 CreateChild 的合约以返回错误。

实现

此变更面向用户的部分包括:

  1. CreateChild API 进行向后兼容的更改,以接受动态优惠列表作为实参。
  2. 一项针对每个合集的选择启用设置,用于允许动态优惠定位相应合集中的内容。(请参阅安全性注意事项中的讨论。)
  3. 可选择加入的父组件的许可名单。(注意:请参阅更新:许可名单移除

(1) 的实现将完全在 Component Manager 本地进行。 实现 (2) 和 (3) 可能需要更改解析 CML 文件的工具。无论哪种方式,都不需要进行跨团队协调。

性能

除了对 CreateChildDestroyChild 的调用之外,此更改应该不会对系统性能产生任何影响,而对 CreateChildDestroyChild 的调用很少见,并且不是性能关键型操作。即使对于这些操作,预计也不会对性能产生不利影响。

工效学设计

这种销毁行为是新引入的,可能会给组件的作者带来人体工程学方面的挑战,因为他们的依赖项可能会突然消失。

从目标组件的角度来看,确定在销毁 offer 后发生了什么并不完全简单:它只会看到渠道关闭。目标组件无法立即判断源组件是被销毁、停止还是有意关闭了通道。为了确定来源是否确实被销毁,目标必须尝试重新连接,并等待来自组件管理器的响应,该响应指示永久性路由失败。

安全注意事项

就其本身而言,此设计会破坏 CFv2 的一个重要安全目标:所有功能路由(或潜在的功能路由)都应声明,以便进行审核。在测试之外使用动态优惠时,需要确保可以通过组件清单以外的其他机制来审核潜在的功能路由。

使用此功能后,驱动程序框架将负责确保可以审核潜在的功能路由空间。目前,规则编码在多个位置:分散的绑定规则、主板驱动程序的源代码、ACPI,可能还有其他位置。这使得审核成为一项挑战。此差距并非新问题,需要由驱动程序框架来解决,但如何解决不在本 RFC 的讨论范围内。

CollectionDecl 中将添加一个选择启用设置,该设置存在时将允许父组件将动态优惠传递给在该集合中创建的组件。这样一来,CML 审核人员就能清楚地知道动态功能正在发挥作用,并且需要参考组件清单之外的来源,才能了解潜在功能路由的范围。

此外,选择启用该功能的功能将由 Component Framework 团队列入许可名单,以便他们确保所有使用情况都符合要求。(注意:请参阅更新:移除许可名单

未来的工作

如果我们发现动态优惠有更多使用场景,可能需要向组件清单架构引入优惠上限。出价上限可用于限制可在运行时创建的动态出价类型。

清单将包含如下声明:

  • “协议 fuchsia.example.Foo 可以从组件 A 动态提供给集合 B 中的任何组件”,或
  • “任何目录功能都可以在集合 C 中的任意两个组件之间动态提供”。

对于初始的 Driver Framework 使用情形,我们认为粗略的收集级选择启用就足够了。在将任何其他客户端列入许可名单之前,必须重新评估此立场。

隐私注意事项

此提案不会使系统访问其之前无法访问的任何数据,因此应该不会对隐私权产生任何影响。

测试

将添加单元测试和集成测试覆盖率,类似于任何其他组件框架功能。驱动程序框架的集成测试将间接提供额外的覆盖率。

此功能不应影响 Component Framework API 的可测试性。使用集合和 Realm 协议已经需要在集成测试设置中与真实的组件管理器实例进行交互,这种情况将继续存在。

文档

由于动态优惠将列入许可名单,并且最初仅由单个客户端使用,因此不应更新高级组件框架概念文档来提及此功能的存在。有关 CreateChildDestroyChildCollectionDecl 更改的文档应足够详细。

在将任何其他客户端列入许可名单之前,必须重新评估此立场。

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

此设计的主要缺点是,它使审核功能路由变得更加复杂,因为并非所有潜在的功能路由都会显示在组件清单中。不过,这是驱动程序框架固有的特性:驱动程序组件的拓扑结构本质上是动态的,因为设备可以随时添加到机器中或从机器中移除。静态出价显然不够,因此故事必须变得更加复杂。动态优惠会尽可能地模仿静态优惠的行为,从而尽量减少这种额外的复杂性。

替代方案:功能令牌

之前曾有人提议使用功能令牌来解决相关问题。该提案可以视为此 RFC 的泛化,其中动态优惠可以在 realm 之间交叉。源组件的父组件“铸造”一个功能令牌,并将其传递给另一个父组件(可能通过一系列中间组件)。接收组件可以将此令牌传递到 CreateChild 中,后者充当授权机制来表明父母双方均同意该优惠。

创建后,此动态优惠应像静态优惠一样运作,但有一个主要例外,即这些动态优惠可以跨领域。静态优惠(以及主要提案中的动态优惠)始终仅限于单个父组件。

跨领域优惠的影响尚不完全明确,我们认为没有必要提供此类优惠,因此拒绝了此选项。

替代方案:提供“实时功能提供商”

fxrev.dev/483166

在主要提案中,动态优惠是惰性数据。优惠表示组件管理器应如何路由功能请求的指令,但本身并不提供对功能的访问权限。

我们可以改为传递功能提供程序,这些渠道可用于直接获取功能。CreateChild 随后会接受一个包含已命名功能提供程序的向量。当动态子组件尝试打开动态提供的功能时,该 Open 请求将转发给功能提供程序。

父组件可以通过以下方式获取此类功能提供程序:

  • 自行实现功能提供程序,
  • 调用 Realm API 以获取其父级或子级公开的功能的功能提供程序,或者
  • 通过渠道从另一个组件接收功能提供程序。

这会解锁与功能令牌相同的使用情形,但具有额外的优势(或可争议的缺点),即父级可以直接向其子级提供功能提供程序:只需在内存中启动服务器并将功能提供程序传递给 CreateChild 即可。

不过,功能提供程序与 CFv2 路由框架的许多重要属性不兼容:功能路由的可审核性、重启之间的持久性等。

替代方案:销毁

当作为优惠来源的动态组件被销毁时,除了销毁优惠之外,还有许多合理的行为。本部分将讨论这些替代方案。

禁止销毁来源

此 RFC 的早期草稿建议,如果组件是动态优惠的来源,则禁止销毁该组件。组件会以这种方式“固定”,直到相应优惠的目标被销毁。

即使从一开始,这似乎也很脆弱,并存在潜在的人体工程学问题(即“无法销毁组件”错误可能很难处理)。不过,随着 RFC-0101 的接受,它变得真正无法使用,因为该 RFC 引入了组件会自动销毁的场景。

销毁传播

另一项提议是让破坏沿着优惠传播。也就是说,如果动态优惠的来源被销毁,目标也会被销毁,依此类推,以递归方式进行。

此选项看起来很可怕,并且与其他 CFv2 行为不一致。在许多情况下,功能不可路由或变得不可用,但即使依赖关系很强,这些情况也不会导致依赖这些功能的组件被强制终止或销毁。组件可以观察到其依赖项不可用,并可选择退出。

更新:移除许可名单

事实证明,此功能可成功在动态组件中强制执行最小权限原则。

随着 web_instance 组件迁移到现代组件框架,此功能可能在 Chromium 代码库中发挥作用。如今,此组件使用旧版组件框架的功能启动动态子项。这些子级的特征集可以在运行时发生变化,并且当启用某些特征时,子级会在旧版组件创建期间通过 additional_services 字段获得额外的协议功能。动态功能可为这种行为提供清晰简单的迁移路径,使其迁移到现代组件框架,方法是允许 web_instance 在运行时决定哪些动态子级将获得额外的功能。

因此,许可名单将被移除,客户端无需获得组件框架团队的批准即可使用该功能。

移除许可名单后,会发生以下变化:

  • 撰写有关如何使用动态优惠的文档。
  • 从 CMC 中移除了 dynamic_offers 受限功能键。
  • 删除 //tools/cmc/build/restricted_featuresdynamic_offers 的许可名单。