RFC-0217:打开包裹跟踪 | |
---|---|
状态 | 已接受 |
领域 |
|
说明 | 保护软件包免遭垃圾回收时,请使用开放软件包跟踪(而不是动态索引)。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-04-18 |
审核日期(年-月-日) | 2023-05-05 |
总结
通过改进软件包垃圾回收 (GC),改善开发者在运行测试或重启修改后的临时组件时的体验。
设计初衷
常见的开发者工作流程,例如:
由于存储空间不足,经常被软件包解析错误中断。这会打破开发者专注,降低对平台的信心,并需要开发者手动触发 GC(可能在重新启动设备后触发)。
这些中断和权宜解决方法是必要的,因为当前的 GC 实现:
- 保护某些不应保护的软件包(需要重新启动才能移除保护)
- 不保护某些应该保护的软件包(使自动触发 GC 变得危险,这会导致当前的做法是避免使用自动 GC 触发器 / 首选手动 GC 触发器)
我们的目标是改进 GC,让开发者能够专心工作,而无需考虑 GC 或存储空间。
利益相关方
教员:
- hjfreyer@google.com
审核者:
- wittrock@google.com(软件交付)
- geb@google.com(组件框架)
- crjohns@google.com(测试)
咨询人员:
- senj@google.com、etrylezaar@google.com、jamesr@google.com
社交:
此 RFC 的初稿已分享给软件开发和组件框架团队的成员。
要求
整个 GC 必须:
- 通过 RFC 170 中的修改,保留 OTA 流程的存储空间用量和向前进度保证。
- 简而言之,允许系统更新程序在 OTA 期间解析的中间软件包(更新软件包和包含传入二进制文件映像的软件包),而无需对下一个系统版本所需的软件包进行 GC 垃圾回收。
- 允许连续运行多个测试(每个测试分别适合设备,并且都来自不同的软件包),而不会耗尽空间。
- 请勿移除组件正在使用的软件包,包括包含正在运行的组件的软件包。
强烈建议您选择符合以下条件的方法:
- 易于实现,主要通过 SWD 完成,因此不需要新的跨团队 API(考虑到当前的开发者时间需求)
- 易于验证(考虑到维护 OTA 存储要求的重要性)
不涵盖的内容:
- 保护某些非必需软件包免遭 GC 的方法(例如,因为预计以后会再次使用这些软件包)。我们希望未来可以采用这些方法,因此所选的方法不应将其排除。
- 确定何时在发生空间不足错误时自动触发 GC(例如,在 CF/full-resolver/pkg-resolver/pkg-cache 还是在 fx 测试中)。
设计
为了符合 Package Sets RFC 中详述的定义和行为,软件交付堆栈尚未升级,因此下面的说明使用了已弃用的术语来更准确地描述系统当前和提议的行为。
定义
- 基础软件包
- “base”或“system_image”软件包(通过启动参数中的哈希来标识)以及其
data/static_packages
文件中(通过哈希)列出的软件包。 - 通常是指运行特定配置所需的最小软件包集。
- “base”或“system_image”软件包(通过启动参数中的哈希来标识)以及其
- 缓存软件包
- “基础”软件包的
data/cache_packages.json
文件中列出的软件包(按哈希值)。 - 通常,我们希望在没有网络连接的情况下仍然可用的非基础软件包。
- “基础”软件包的
- 保留的索引
- 将在 OTA 期间下载、在 OTA 过程中使用或下一个系统版本所必需的软件包列表(按哈希值)。
- 在 OTA 过程中由 system-updater 组件操纵,以满足 OTA 存储空间要求。
- 动态索引
- 从软件包路径(可在软件包的
meta/package
文件中找到,通常与用于解析软件包的网址的路径相同)到最近针对该路径解析的软件包的哈希值的映射。 - 在启动时,动态索引会预先填充缓存软件包(只要路径相同但哈希值不同的软件包之后不进行解析,就会保护它们免遭 GC 攻击)。
- 解析时,如果软件包位于保留的索引(按哈希值标识软件包)中,则不会添加该软件包到动态索引中(因此不会逐出路径相同但哈希值不同的软件包)。
- 从软件包路径(可在软件包的
- 软件包 blob
- 软件包所需的所有 blob。
- 以递归方式加载的 meta.far 和内容 blob,以及所有子软件包的软件包 blob。
- 根据这一定义,保护软件包免遭 GC 保护,可以保护其所有子软件包。作为软件包本身,子软件包本身不一定会独立于超级软件包提供的保护受到保护。
当前算法
- 确定所有常驻 blob 的集合,
Br
- 暂停解析非驻留软件包
- 确定所有受保护 blob 的集合
Bp
,它们是以下各项的软件包 blob:- 基础软件包
- 保留的索引包
- 动态索引软件包
- 告诉 blobfs 删除集合差
Br - Bp
- 取消暂停非本地软件包的解析
建议的算法
创建“开放软件包索引”,用于跟踪哪些软件包的 [子目录]具有打开的 fuchsia.io/[Node|Directory]
连接。如需了解可能的实现,请参阅 https://fxrev.dev/817432(顺便提一下,此实现会对用于提供软件包目录的数据结构进行去重,这应该可以节省至少 1 MB 的内存)。在 pkg-cache(提供所有临时软件包的软件包目录的组件)中使用开放软件包索引。
让 pkg-resolver 公开一个名为 fuchsia.pkg.PackageResolver-ota
的附加 fuchsia.pkg/PackageResolver
capability。将此功能路由到系统更新程序(且仅传送到系统更新程序)而非当前的 fuchsia.pkg.PackageResolver
功能。通过此功能解析的软件包必须在解析之前位于保留的索引中,并且将从开放软件包索引中排除(通过向 fuchsia.pkg/PackageCache.[Open|Get]
添加标志)。
创建一个“写入软件包索引”,用于跟踪当前正在写入存储空间的软件包。这实际上是动态索引,只不过它会在软件包解析后停止跟踪(此时,开放软件包索引或保留的索引将涵盖这些软件包)。
使用相同的 GC 算法,但需要将动态索引替换为写入软件包索引和开放软件包索引,因此,受保护的 blob 现在是以下 blob 的 blob:
- 基础套餐
- 缓存软件包
- 保留的索引软件包
- 编写软件包索引软件包
- 打开软件包索引软件包
这满足要求:
利用 RFC 170 中的修改,保留 OTA 流程的存储空间用量和向前进度保证。
OTA 过程中解析的所有软件包都将从开放软件包索引中排除(类似于当前 OTA 解析从动态索引中排除的方式,首次添加到保留的索引中),因此仍会满足存储空间用量和向前进度的要求。
允许连续运行多个测试(每个测试分别适合设备,并且都来自不同的软件包),而不会耗尽空间。
在连续从多个不同软件包运行测试时,现在可以触发 GC 以防止出现空间不足错误。该动态索引用于保护每个测试软件包的最新解析版本(软件包在解析时按路径添加,并且仅在重新启动时移除),因此之前运行的测试永远不会被 GC 处理,但现在开放软件包索引将在测试软件包的上次连接关闭后停止保护测试软件包。
请勿移除组件正在使用的软件包,包括包含正在运行的组件的软件包。
以前,如果启动了一个组件,然后解析了其他版本的后备软件包,那么无论该组件是否仍在运行,该组件的软件包都会从动态索引中逐出。现在,由于正在运行的组件保持与其软件包目录的连接,因此开放软件包索引将保护该组件的软件包免遭 GC。
索引 | 软件包添加操作 | 文件包移除操作 |
---|---|---|
底座 | 产品装配 | never |
缓存 | 产品装配 | never |
留存 | system-updater 在 OTA 期间设置 | system-updater 在 OTA 期间更新/清除 |
文案 | 软件包解析开始 | 软件包解析结束 |
打开 | 非 OTA 软件包解析结束 | 上次与软件包目录的连接关闭 |
性能
动态索引使用的内存与自启动以来解析的不同临时软件包的数量成正比(按软件包路径分组)。开放软件包索引使用的内存与具有开放连接的临时软件包的数量成正比(按软件包哈希分组)。由于当前临时软件包的使用方式,这些内存占用量很小且大小相近(在运行许多不同的测试软件包的情况下,开放软件包索引会较小,因为动态索引实际上会泄露这些条目)。通过对用于提供软件包目录的数据结构进行重复信息删除,内存占用量的任何差异都应该小于节省的内存。
工效学设计
将动态索引替换为 Open Package 索引可使系统更易于理解和操作:
- 开放软件包索引的状态(以及 GC 的行为)仅取决于当前正在使用的软件包,而动态索引的状态取决于启动后发生的每个软件包解析的顺序。
- 与动态索引不同,开放软件包索引不依赖于软件包的路径(可在软件包的
meta/package
文件中找到)。用户通常不知道软件包路径是一个概念(他们通常知道软件包网址的路径组成部分,但meta/package
路径可以有所不同),现在 GC 行为将不再依赖于它。这修复了以下问题:来自不同代码库但具有相同软件包路径的相关软件包会竞争 GC 保护(通过将彼此从动态索引中逐出来)。这还会移除软件包路径上最后剩下的一个依赖项。 - 用户无需再担心 GC 会从当前正在执行的组件中删除软件包。
安全注意事项
无影响。
隐私注意事项
无影响。
测试
一般来说,对于软件包解析和 GC,以及 OTA 和 GC 之间的相互作用,进行了大量测试。系统会检查这些测试,确保它们仍然有意义且已完成。
文档
现有的 GC 文档将会更新。
诊断
写入和打开的软件包索引将通过 Inspect 公开。基本软件包和缓存软件包以及保留的索引已包含在 pkg-cache 的检查数据中。
缺点、替代方案和未知情况
根据要求,我们认为此解决方案使 GC 比目前更正确。不过,这种方法还存在一些未知情况和缺点。
缺点
当前的实现会尝试保护当前未使用但预计会再次解析的临时软件包,以避免稍后重新下载 blob。建议的实现没有任何此类保护。 这应该不破坏任何工作流,因为即使在当前的实现中,临时解析仍然需要通过网络访问来检查代码库元数据(这意味着设备仍应能够重新下载 blob),并且 GC 很少触发,因此需要重新下载 blob 的情况也很少见。此外,所提议的方法不会阻止将来重新添加预测性保护。
上述缺点的一个后果是,现在在依赖于多个软件包但并未保持与这些软件包目录打开的连接的工作流中间触发 GC 时必须小心谨慎。理论上,目标工作流应该能够使所有必需的软件包保持打开状态,但主机上编排的工作流可能会让这更加困难。
与当前的实现相反,当解析不同版本的软件包(由 meta/package
中的路径标识)时,缓存软件包仍将受到保护。这意味着,在解析不同版本并触发 GC 之后,缓存回退(在网络不再可用等情况下使用)仍会成功。例如,如果非缓存版本以意想不到的方式修改了配置文件,则情况会很糟糕。这是可以接受的,因为如果 GC 未触发且很少触发 GC,此问题目前已经出现。
替代选项
让系统更新程序在触发 GC 之前关闭与中间软件包的连接,而不是为系统更新程序提供从开放软件包跟踪中排除的特殊 fuchsia.pkg/PackageResolver
功能。
开放软件包索引将会异步更新(每当检测到连接已关闭时),系统更新程序无法得知这是何时发生的。我们可以创建一个 API 来查询开放软件包索引,但目标不是让系统更新程序无条件地 GC 中间软件包,目标是让系统更新程序在唯一的开放连接是与系统更新程序的连接时,对中间软件包进行 GC(假设中间软件包同时也是当前系统的基础软件包),而软件包传送机器的客户端不知道如何对中间软件包进行 GC。此外,系统更新程序已经在通过保留的索引手动跟踪其解析的包裹,因此,最好从自动跟踪中排除其解析。
不要为系统更新程序提供从开放软件包跟踪中排除的特殊 fuchsia.pkg/PackageResolver
功能,而继续为系统更新程序提供标准功能,并从开放软件包跟踪中排除保留的软件包。
这种方法可能会导致用于运行组件的软件包被 GC 回收。请注意以下几点:
- 开发者修改测试
- 系统会自动暂存 OTA,并且测试位于系统的缓存软件包中,因此会添加到保留的索引中
- 开发者运行测试,测试软件包不会添加到开放软件包索引中,因为它位于保留的索引中
- 系统会自动为另一个系统版本暂存另一个 OTA(可能是因为第一次尝试失败),这会从保留的索引中移除测试软件包
- 触发了 GC,从正在运行的测试中删除 blob
未知
开放包裹跟踪功能可保护所有处于开放状态的包裹。某些组件持有软件包目录句柄的时间可能会超出我们的预期。当软件包解析因空间不足错误而失败时,这会导致类似于开发者现在所见问题的问题。您需要找到并修复任何此类实例(使用 k zx ch
等控制台命令通常非常简单)。这对用户设备而言不会造成问题,因为在用户设备上,只有系统更新程序使用临时分辨率。