RFC-0107:动态优惠 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 允许在 CFv2 中在运行时创建优惠。 |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2021-05-24 |
审核日期(年-月-日) | 2021 年 6 月 24 日 |
总结
在组件框架 v2 (CFv2) 中,父级组件可以在运行时使用集合向拓扑添加动态子组件。但是,无法在运行时配置为此类动态子组件提供的一组功能。优惠是在父组件的清单中声明的,在父组件开始运行时,优惠是不可变的。
父组件的清单统一对待给定集合中的所有动态子项;它甚至无法谈及集合中的单个组件。这意味着存在某些限制:
- 无法为单个动态子级提供 capability。为集合提供 capability 会为集合中的所有动态子级提供相应功能。
- 您无法将单个动态子组件提供另一个组件的功能。如果从集合中提供某项功能,则会创建一个汇总 FIDL 服务,该服务允许连接到该集合中的任何动态子项。
本文档中提出了动态优惠,可在运行时将其添加到组件拓扑中。
利益相关方
- 在运行时构建动态拓扑的 CFv2 用户。他们将由驱动程序框架团队介绍。
- 组件框架区域所有者:可以验证设计是否合理且不会与任何未来计划发生冲突。
- 安全性,负责确保此功能不会破坏系统的安全属性。
设计初衷
大多数 CFv2 用例都涉及一个静态拓扑,其中组件会协同工作以完成某项任务。所有这些组件之间的身份和关系是预先知道的,可以硬编码到组件清单中。
在其他一些用例中,开发者并不知道将运行的确切组件集,但知道这些动态组件彼此之间如何相互关联。开发者可以将所有动态组件分组到集合中,其中给定集合中的每个组件都提供同一组功能。
但是,CFv2 的某些用户希望在运行时构建复杂的拓扑,为每个动态子级提供一组独特的功能,并在动态子级之间路由功能。
使用场景:驱动程序即组件
在驱动程序框架的驱动程序即组件项目中,动态实例化的驱动程序组件依赖于其他动态实例化的驱动程序组件提供的功能。我们将从一个驱动程序组件提供的功能称为另一个驱动程序间功能。
CFv2 本身不支持驱动程序间功能。驱动程序框架团队通过在 driver_manager
本身中实现类似功能路由的功能解决了此问题。这种解决方法存在一些非常粗略的差异,并暴露了驱动程序间功能和“常规”功能之间令人困惑的差异:
- 组件通过在传入的命名空间中打开路径来连接到“常规”功能,但通过特定于驱动程序框架的
exposed_dir
字段连接到驱动程序间功能。 - 在给定
exposed_dir
后,接收组件可以访问由其依赖项实现的所有协议。driver_manager
不会仅降低接收器对所需协议的访问权限。驱动程序框架团队可以解决此问题,但这样做会导致组件框架机制进一步重复。 - 驱动程序组件需要在其清单的
use
部分中声明它们所依赖的“正常”功能,但不应以相同的方式列出驱动程序间功能。否则,组件管理器将无法找到满足use
的offer
。- 目前,组件管理器主要会忽略这些无关的
use
,但这可能会在短期内引起混淆,而这些无关use
从长远来看可能会变成错误。
- 目前,组件管理器主要会忽略这些无关的
驱动程序作者不应将“常规”功能视为与驱动程序间的功能不同,因此应尽快消除这种区别。我们等待的时间越长,当模型发生变化时,需要迁移的驱动程序就越多。
设计
此方案扩展了 fuchsia.sys2.Realm/CreateChild
API,以接受 OfferDecl
列表,这些列表用于描述除了通常提供的功能之外,还将为新创建的子级提供的其他功能。我们将以这种方式指定的 OfferDecl
称为动态优惠。以常规方式指定(即在组件清单中列出)的 OfferDecl
将称为静态优惠。
任何类型的优惠(即协议、目录等)都可以用作动态优惠。
动态优惠始终定位到新创建的子组件。因此,定位到某个组件的一组动态优惠在创建组件时是已知的,在此之后是不可变的。(是否必须实际指定 OfferDecl
的 target
字段将在 API 审核中确定。)
任何可对静态优惠有效的来源也适用于动态优惠。此外,与静态优惠不同的是,动态优惠可以使用“同级”动态子组件(即与目标相同的组件)作为来源。如果动态优惠有一个动态子级作为来源,则目标必须与来源位于同一集合中。
创建子组件后,动态优惠的行为应与静态优惠完全相同,包括考虑到组件关闭顺序等因素。
破坏
动态优惠会在其来源或目标被销毁时销毁。优惠被销毁后,系统的行为将与它从未存在过一样。
在可能的情况下,使用动态产品/服务的用户应优先销毁目标,而非来源。如果优惠是被销毁的目标(如果优惠落入森林并且周围没有组件听到它),则从拓扑中移除优惠的过程将非常简单。不过,有时此方法也不适用,来源会在目标之前被销毁。值得注意的是,在使用单次运行集合(在 RFC-0101 中引入)时,动态组件会在终止后立即销毁(可能随时发生)。
即使它是被销毁的,过程也非常简单:
- 组件管理器会关闭源组件。这可以防止目标打开与来源的任何新连接,并指示运行程序停止源组件(如果该组件正在运行)。
- 当来源组件停止时,来源与目标之间的所有通道都将关闭。
- 系统会同时从拓扑中移除来源组件和动态优惠。
从目标的角度来看,与该功能对应的渠道会关闭,并且任何尝试重新获取该功能的尝试都将永久失败,就像最初没有路由该 capability 一样。
此 RFC 没有提供重新创建此类优惠的机制。
防止依赖循环
CFv2 要求组件以及它们之间的强项形成有向无环图,以便组件可以按依赖关系顺序正常关闭。对于静态优惠,我们可以通过查看组件清单来确保没有依赖项循环。
对于动态优惠,以下原则足以确保无循环依赖关系图:
- 动态组件创建顺序是严格的总顺序。顺便说,不会有两个动态组件同时添加到拓扑中。
- 动态优惠始终与其目标组成部分同时创建。
- 所有优惠(静态和动态)都是不可变的。
- 所有优惠(静态和动态)都必须始终具有有效的来源和有效的目标。在动态优惠创建时,
CreateChild
会检查此属性;每当来源或目标被销毁时,优惠也会被销毁。
总而言之,这意味着动态优惠的来源始终比其目标早。如果来源时间较新,则意味着创建目标时它不存在,因此优惠在创建时会包含无效的来源。由于“创建顺序”是严格的总顺序,因此不可能出现依赖循环。
处理无效路由
截至撰写本文时,组件管理器不会主动拒绝具有无效路由配置的组件清单,也就是说,如果某项功能的来源没有提供该功能,或者某个组件没有收到针对其所用功能的优惠,则在创建组件时不会引发任何错误。只有在组件尝试打开无效路由的功能时,才会引发错误。
本着“动态优惠的行为应该与静态优惠类似”,我们在这里实现相同的行为。组件可能会为来源未提供的功能创建动态优惠。同样,父组件可以创建动态子组件,而不为其使用的每个功能提供优惠。
将来,如果组件管理器在这些情况下更改为提前返回错误,我们也将更改 CreateChild
的协定以返回错误。
实现
此更改面向用户的界面包括:
- 对
CreateChild
API 进行了向后兼容的更改,可接受动态优惠列表作为参数。 - 每个合集的选择启用设置,以允许动态优惠定位到该集合中的组件。(请参阅安全注意事项中的讨论。)
- 可选择启用的父组件的许可名单。(注意:请参阅更新:移除许可名单)
(1) 的实现将完全在组件管理器本地执行。实现 (2) 和 (3) 可能涉及对解析 CML 文件的工具进行更改。无论采用哪种方式,应该都不需要跨团队协调。
性能
此更改应该不会对 CreateChild
和 DestroyChild
调用之外的系统性能产生任何影响,它们属于极少数且对性能要求不高的操作。即使执行这些操作,也不会对性能产生不利影响。
工效学设计
提供销毁行为是一项新行为,对于依赖项突然消失的组件作者,可能会带来人体工程学挑战。
从目标组件的角度来看,确定优惠销毁后发生的情况并非完全简单,只能看到渠道关闭。目标组件无法立即判断源组件是被销毁、停止还是有意关闭通道。为了确定来源是否确实被销毁,目标必须尝试重新连接并等待来自组件管理器的响应(指明路由永久失败)。
安全注意事项
这种设计本身就破坏了 CFv2 的一个重要安全目标:所有功能路由(或潜在的功能路由)都应进行声明,以便对其进行审核。在测试之外对动态优惠的任何使用都需要确保可以通过组件清单以外的其他某种机制审核潜在的功能路由。
使用此功能即表示,驱动程序框架负责确保可以审核潜在功能路线的空间。目前,这些规则会在许多位置进行编码:分散式绑定规则、板驱动程序源代码、ACPI 等等。这使得审核成为一项挑战。这一差距并不是新的,需要通过驱动程序框架加以解决,但具体操作方法不在本 RFC 的讨论范围内。
系统会向 CollectionDecl
中添加一项选择启用设置,如果使用此设置,父组件便可将动态优惠传递给在该集合中创建的组件。这样,CML 审核人员就能清楚地了解动态优惠正在发挥作用,并且需要查询组件清单以外的来源,以了解潜在功能路由的范围。
此外,组件框架团队会将选择启用功能列入许可名单,以便他们确保所有使用方式都合适。(注意:请参阅更新:移除许可名单)
后续工作
如果我们发现更多动态优惠的用例,可能需要在组件清单架构中引入优惠上限。优惠上限可用于限制可在运行时创建的动态优惠的种类。
清单将包括如下语句:
- “协议
fuchsia.example.Foo
可以从组件A
动态提供给集合B
中的任何组件”,或者 - “任何目录功能都可以在集合
C
中的任意两个组件之间动态提供”。
对于初始驱动程序框架用例,我们认为粗略的集合级选择启用就足够了。在将任何其他客户端列入许可名单之前,必须重新评估此立场。
隐私注意事项
此方案不会向系统中的任何部分公开其无权访问的数据,因此它不会对隐私产生任何影响。
测试
将添加单元测试和集成测试覆盖范围,类似于任何其他组件框架功能。额外的覆盖率将由驱动程序框架的集成测试间接提供。
此功能既不会帮助也不会损害 Component Framework API 的可测试性。使用集合和 Realm
协议已经需要在集成测试环境中使用真实的组件管理器实例,未来也是如此。
文档
由于动态优惠将列入许可名单,并且最初仅由单个客户端使用,因此不应更新高级组件框架概念文档以提及此功能的存在。有关对 CreateChild
、DestroyChild
和 CollectionDecl
的更改的文档应该足够了。
在将任何其他客户端列入许可名单之前,必须重新评估此立场。
缺点、替代方案和未知情况
这种设计的主要缺点是,它会使功能路由的审核变得更加复杂,因为并非所有潜在的功能路由都会出现在组件清单中。不过,这是驱动程序框架所固有的:驱动程序组件的拓扑本质上是动态的,因为可以即时在机器上添加和移除设备。静态优惠显然不足,因此故事必须变得更加复杂。动态优惠会尝试尽可能地镜像静态优惠的行为,从而将这种额外的复杂性降至最低。
替代方案:功能令牌
人们之前已提出功能令牌来解决相关问题。该方案可以视为此 RFC 的泛化,其中动态优惠可以跨越领域。来源组件的父组件“创建”功能令牌,并将其传递给另一个父组件(可能通过一系列中间组件)。接收组件可以将此令牌传递到 CreateChild
,从而充当授权机制来证明双方家长同意使用优惠。
创建后,此动态优惠的行为应类似于静态优惠,但有一个主要区别是,这些动态优惠可以在不同领域之间跨越。静态优惠(以及在主提案中,动态优惠)始终是单个父组件的本地优惠。
跨领域优惠的影响并不完全清楚,而且我们认为没有必要这样做,因此已拒绝此选项。
替代方案:提供“实时功能提供商”
在主提案中,动态优惠是过时数据。优惠表示组件管理器应如何路由对某项 capability 的请求的说明,但不自行提供对相应 capability 的访问权限。
我们可以改为传递“功能提供程序”,即可直接获取功能的渠道。然后,CreateChild
将接受已命名功能提供程序的矢量。当动态子组件尝试打开动态提供的 capability 时,系统会将该 Open
请求转发到 capability 提供程序。
父组件可以通过以下方式获取此类 capability 提供程序:
- 自行实现 capability Provider,
- 调用
Realm
API 以获取其父项或子项的某个公开 capability 的功能提供程序,或者 - 通过通道接收来自其他组件的功能提供程序。
这将解锁与功能令牌相同的用例,但具有父级可以直接为子级提供 capability provider 的附加好处(或者有点缺点):只需在内存中启动服务器并将 capability provider 传递给 CreateChild
即可。
但是,功能提供程序与 CFv2 路由框架的许多重要属性不兼容:功能路由的可审核性、重启之间的持久性等。
替代方案:销毁
当作为优惠来源的动态组件被销毁时,除了销毁优惠之外,还存在许多合理的行为。本部分将讨论这些替代方案。
禁止销毁来源
此 RFC 的早期草稿仅建议禁止销毁动态优惠的来源组件。组件将以这种方式“固定”,直到这些组件的目标被销毁。
即使从一开始,这似乎也很脆弱,并且存在潜在的人体工程学问题(例如,“组件无法销毁”错误可能很难处理)。但是,随着 RFC-0101 的接受,此 API 变得越来越不可行,RFC-0101 引入了自动销毁组件的场景。
销毁传播
另一个方案是让销毁功能随报价一起传播。也就是说,如果动态优惠的来源被销毁,则目标也会被销毁,依此类推。
此选项似乎很吓人,并且与其他 CFv2 行为不一致。在很多情况下,功能无法路由或不可用,并且这些情况都不会强制终止或销毁依赖于这些功能的组件,即使依赖项很高也是如此。组件可以观察到其依赖项不可用这一事实,并在其选择时退出。
更新:移除许可名单
事实证明,此功能在动态组件中强制执行最小权限方面是成功的。
通过将 web_instance
组件迁移到新组件框架,此功能可在 Chromium 代码库中使用。如今,此组件使用旧版组件框架的功能启动动态子级。这些子项的功能集在运行时可能会发生变化,当某些功能启用时,这些子项的功能集会在旧版组件创建期间通过 additional_services
字段获得额外的协议功能。动态优惠允许 web_instance
在运行时决定哪些动态子项将获得额外功能,从而为这种行为提供了简单明了的迁移路径。
因此,许可名单将被移除,客户无需获得组件框架团队的批准即可使用该功能。
为了移除许可名单,将发生以下更改:
- 撰写有关如何使用动态优惠的文档。
- 从 CMC 中移除了
dynamic_offers
受限功能密钥。 - 删除
//tools/cmc/build/restricted_features
中的dynamic_offers
许可名单。