RFC-0217:打开包裹跟踪 | |
---|---|
状态 | 已接受 |
区域 |
|
说明 | 在保护软件包免受垃圾回收影响时,请使用开放式软件包跟踪,而不是动态索引。 |
问题 | |
Gerrit 更改 | |
作者 | |
审核人 | |
提交日期(年-月-日) | 2023-04-18 |
审核日期(年-月-日) | 2023-05-05 |
摘要
通过改进软件包垃圾回收 (GC),改善了运行测试或重启经过修改的短时性组件时的开发者体验。
设计初衷
常见的开发者工作流,例如:
- 连续运行多个测试
- Repeatedly运行和修改同一测试
- 反复运行和修改同一临时组件
由于存储空间不足,经常会因软件包解析错误而中断。这会打断开发者的注意力,降低对平台的信心,并要求开发者手动触发 GC,可能需要在重新启动设备后执行此操作。
由于当前的 GC 实现存在以下问题,因此这些中断和权宜解决方法是必要的:
- 保护了不应保护的某些软件包(需要重新启动才能移除保护)
- 不会保护一些应保护的软件包(自动触发 GC 会很危险,这导致目前的做法是避免自动 GC 触发器 / 首选手动 GC 触发器)
我们的目标是改进 GC,使这些工作流程能够正常运行,而开发者无需考虑 GC 或存储空间。
利益相关方
教员:
- hjfreyer@google.com
Reviewers:
- wittrock@google.com(软件交付)
- geb@google.com(组件框架)
- crjohns@google.com(测试)
咨询了:
- senj@google.com、etrylezaar@google.com、jamesr@google.com
社交:
我们已与软件开发团队和组件框架团队的成员分享了此 RFC 的早期草稿。
要求
GC 作为一个整体必须:
- 使用 RFC 170 中的修改来维护 OTA 流程的存储用量和向前进度保证。
- 简而言之,允许 system-updater 在 OTA 期间回收其解析的中间软件包(更新软件包和包含传入二进制映像的软件包),而不回收下一个系统版本所需的软件包。
- 允许连续运行多个测试(每个测试都分别适合在设备上运行,并且都来自不同的软件包),而不会耗尽空间。
- 请勿移除正在被组件使用的软件包,包括包含正在运行的组件的软件包。
我们强烈建议采用以下方法:
- 易于实现(主要由 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 和 content 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 路由到 system-updater(并且仅路由到 system-updater),而不是当前的 fuchsia.pkg.PackageResolver
capability。通过此 capability 解析的软件包必须在解析之前位于保留的索引中,并且将从打开的软件包索引中排除(通过向 fuchsia.pkg/PackageCache.[Open|Get]
添加标志)。
创建一个“写入软件包索引”,用于跟踪当前正在写入存储空间的软件包。这实际上就是动态索引,但它会在解析软件包后停止跟踪软件包(此时,软件包将由打开的软件包索引或保留的索引涵盖)。
使用相同的 GC 算法,但将动态索引替换为写入和打开的软件包索引,因此受保护的 blob 现在是以下 blob:
- 基础软件包
- 缓存软件包
- 保留的索引软件包
- 编写软件包索引软件包
- 打开软件包索引软件包
这满足以下要求:
使用 RFC 170 中的修改,确保 OTA 流程的存储用量和向前进度保证。
OTA 流程中解析的所有软件包都会从公开软件包索引中排除(类似于 OTA 解析目前通过先添加到保留索引中而从动态索引中排除的方式),因此仍会满足存储用量和向前推进要求。
允许连续运行多个测试(每个测试都分别适合在设备上运行,并且都来自不同的软件包),而不会耗尽空间。
现在,连续运行来自多个不同软件包的测试时,可以触发 GC 以防止出现空间不足错误。动态索引用于保护每个测试软件包的最新解析版本(软件包在解析时按路径添加,并且仅在重新启动时移除),因此之前运行的测试永远不会被 GC,但现在,打开的软件包索引会在其最后一个连接关闭后停止保护测试软件包。
不移除正在被组件使用的软件包,包括包含正在运行的组件的软件包。
以前,如果某个组件已启动,然后解析了其他版本的后备软件包,无论该组件是否仍在运行,其软件包都会从动态索引中驱逐出。现在,由于正在运行的组件会保留与其软件包目录的连接,因此公开的软件包索引会保护组件的软件包免受 GC 影响。
索引 | 文件包添加操作 | 软件包移除操作 |
---|---|---|
Google Base | 产品组装 | 从未用过 |
缓存 | 产品组装 | 从未用过 |
保留 | OTA 期间设置的 system-updater | system-updater 在 OTA 期间更新/清除 |
文案 | 开始解析软件包 | 软件包解析结束 |
打开 | 非 OTA 软件包解决方案结束 | 关闭与软件包目录的最后连接 |
性能
动态索引使用的内存与自启动以来解析的不同临时软件包数量成正比(按软件包路径分组)。打开的软件包索引使用的内存与具有打开连接的暂时性软件包数量成正比(按软件包哈希分组)。由于当前的暂时性软件包使用方式,这些内存占用小且大小相近(如果运行许多不同的测试软件包,打开的软件包索引会更小,因为动态索引会有效地泄露这些条目)。内存占用量的任何差异都应小于通过删除用于分发软件包目录的数据结构而节省的内存。
工效学设计
将动态索引替换为开放式软件包索引后,系统更易于理解和操作:
- 打开的软件包索引的状态(以及 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 很少触发,此问题目前就已经存在。
替代方案
请勿向 system-updater 提供从打开的软件包跟踪中排除的特殊 fuchsia.pkg/PackageResolver
功能,而是让 system-updater 在触发 GC 之前关闭与中间软件包的连接。
打开的软件包索引会异步更新(每当它注意到连接已关闭时),并且系统更新程序无法知道何时发生了这种情况。我们可以创建一个 API 来查询打开的软件包索引,但目标不是让 system-updater 无条件地对中间软件包进行 GC,而是在以下情况下对中间软件包进行 GC:唯一的打开连接是与 system-updater 的连接(假设中间软件包也是当前系统的基础软件包),并且软件包服务机制不知道连接的客户端端点由谁持有。此外,system-updater 已通过保留的索引手动跟踪其已解析的软件包,因此将其解析结果从自动跟踪中排除是合理的。
不要向 system-updater 提供从开放式软件包跟踪中排除的特殊 fuchsia.pkg/PackageResolver
功能,而是继续向 system-updater 提供标准功能,并将保留的软件包从开放式软件包跟踪中排除。
这种方法可能会导致运行组件的软件包被 GC。请注意以下几点:
- 开发者修改测试
- 系统会自动分阶段发布 OTA,并且测试位于系统的缓存软件包中,因此会添加到保留的索引中
- 开发者运行测试,测试软件包未添加到打开的软件包索引,因为它位于保留的索引中
- 系统会自动为其他系统版本预演另一个 OTA(可能是因为第一次尝试失败),从而从保留的索引中移除测试软件包
- 触发了 GC,从正在运行的测试中删除了 blob
未知
打开文件包跟踪功能可保护任何处于打开连接状态的文件包。某些组件可能会比预期更长时间地保留软件包目录句柄。这会导致与开发者目前因空间不足错误而导致的软件包解析失败问题类似的问题。您需要找到并修正所有此类实例(通常使用 k zx ch
等控制台命令即可轻松完成)。这对用户设备来说不是问题,因为在用户设备上,只有系统更新程序使用暂时性分辨率。