RFC-0217:开放式包裹跟踪

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 必须:

  1. 通过 RFC 170 中的修改,保持 OTA 流程的存储空间用量和向前进度保证。
    • 简而言之,允许系统更新程序在 OTA 期间对它解析的中间软件包(更新软件包和包含传入二进制映像的软件包)进行垃圾回收,而无需对下一个系统版本所需的软件包进行垃圾回收。
  2. 允许连续运行多个测试(每个测试单独适合设备,并且全部来自不同的软件包),而不会耗尽空间。
  3. 不移除正被组件使用的软件包,包括包含正在运行的组件的软件包。

人们非常偏好以下方法:

  • 易于实现,主要通过 SWD 实现,并且不需要新的跨团队 API(鉴于目前对开发者时间的需求)
  • 易于验证(鉴于保持 OTA 存储空间要求的重要性)

不在范围内:

  • 保护一些非必需软件包免受垃圾收集的机制(例如,因为这些软件包预计会在稍后再次使用)。我们希望未来能够采用这些方法,因此所选方法不应排除这些方法。
  • 确定何时自动触发垃圾回收以响应空间不足错误(例如,在 CF/full-resolver/pkg-resolver/pkg-cache 中与在 fx 测试中)。

设计

软件交付堆栈尚未升级为符合软件包集 RFC 中详述的定义和行为,因此下文中的说明使用已弃用的术语来更准确地描述系统当前和提议的行为。

定义

  • 基础套餐
    • “基本”或“system_image”软件包(通过启动实参中的哈希进行标识)及其 data/static_packages 文件中列出的软件包(通过哈希进行标识)。
    • 通常旨在提供运行特定配置所需的最小软件包集。
  • 缓存软件包
    • “基本”软件包的 data/cache_packages.json 文件中列出的软件包(按哈希值)。
    • 通常是即使没有网络也希望可用的非基础软件包。
  • 留存指数
    • 在 OTA 期间下载并用于 OTA 过程或对于下一个系统版本必需的软件包(按哈希)的列表。
    • 在 OTA 过程中由 system-updater 组件操纵,以满足 OTA 存储空间要求。
  • 动态索引
    • 从软件包路径(位于软件包的 meta/package 文件中,通常与用于解析软件包的网址路径相同)到最近为相应路径解析的软件包的哈希值的映射。
    • 在启动时,动态索引会预先填充缓存软件包(只要稍后未解析出具有相同路径但哈希不同的软件包,这些缓存软件包就会受到 GC 的保护)。
    • 如果软件包在解析时位于保留索引(通过哈希识别软件包)中,则不会将其添加到动态索引中(因此不会逐出具有相同路径但哈希不同的软件包)。
  • 软件包 blob
    • 软件包所需的所有 blob。
    • meta.far 和内容 blob,以及所有子软件包的软件包 blob(以递归方式)。
    • 根据此定义,保护软件包免受垃圾回收的保护也适用于其所有子软件包。子软件包本身可能受保护,也可能不受保护,具体取决于超级软件包提供的保护。

当前算法

  1. 确定所有常驻 blob 的集合,Br
  2. 暂停解析非常驻软件包
  3. 确定所有受保护 blob 的集合 Bp,这些 blob 是以下内容的软件包 blob:
    • 基础软件包
    • 保留的索引软件包
    • 动态索引软件包
  4. 告知 blobfs 删除差集 Br - Bp
  5. 取消暂停非常驻软件包的解析

建议的算法

创建一个“打开的软件包索引”,用于跟踪哪些软件包具有包含打开的 fuchsia.io/[Node|Directory] 连接的 [子]目录。如需了解可能的实现,请参阅 https://fxrev.dev/817432(顺便说一句,此实现会重复使用用于提供软件包目录的数据结构,这应该至少可以节省 1 MB 的内存)。使用 pkg-cache 中的开放软件包索引(用于提供所有临时软件包的软件包目录的组件)。

让 pkg-resolver 公开一个名为 fuchsia.pkg.PackageResolver-ota 的额外 fuchsia.pkg/PackageResolver 功能。将此功能路由到系统更新程序(且仅路由到系统更新程序),而不是当前的 fuchsia.pkg.PackageResolver 功能。此功能解析的软件包必须在解析之前位于保留的索引中,并且将从开放软件包索引中排除(通过向 fuchsia.pkg/PackageCache.[Open|Get] 添加标志)。

创建一个“写入软件包索引”,用于跟踪当前正在写入存储空间的软件包。这实际上是动态索引,只不过它会在软件包得到解决后停止跟踪这些软件包(此时这些软件包将由开放软件包索引或保留索引涵盖)。

使用相同的 GC 算法,但将动态索引替换为写入和打开软件包索引,因此受保护的 blob 现在是以下 blob:

  • 基础软件包
  • 缓存软件包
  • 保留的索引软件包
  • 编写软件包索引软件包
  • 打开软件包索引软件包

这满足了要求

  1. 通过 RFC 170 中的修改,维护 OTA 流程的存储空间使用情况和向前进度保证。

    OTA 过程中解析的所有软件包都将从开放软件包索引中排除(类似于目前通过先添加到保留索引中,将 OTA 解析从动态索引中排除的方式),因此存储空间用量和向前进度要求仍将得到满足。

  2. 允许连续运行多个测试(每个测试都能单独在设备上运行,并且全部来自不同的软件包),而不会耗尽空间。

    现在,当连续运行来自多个不同软件包的测试时,可以触发 GC 以防止空间不足错误。用于保护每个测试软件包的最新解析版本的动态索引(软件包在解析时按路径添加,仅在重新启动时移除),因此之前运行的测试永远不会被垃圾回收,但现在,一旦测试软件包的最后一个连接关闭,打开的软件包索引将停止保护这些测试软件包。

  3. 不移除正被组件使用的软件包,包括包含正在运行的组件的软件包。

    之前,如果启动了某个组件,然后解析了支持软件包的不同版本,则无论该组件是否仍在运行,其软件包都会从动态索引中逐出。现在,由于运行中的组件会保持与其软件包目录的连接,因此组件的软件包将受到打开的软件包索引的保护,免受垃圾回收 (GC) 的影响。

索引 添加软件包操作 软件包移除操作
Google Base 产品组装 从未用过
缓存 产品组装 从未用过
保留 OTA 期间设置的 system-updater 在 OTA 期间,system-updater 会更新/清除
写作 软件包解析开始 问题解决结束
打开 非 OTA 软件包解析结束 与软件包目录的最后一次连接关闭

性能

动态索引使用的内存与自启动以来解析的不同临时软件包的数量(按软件包路径分组)成正比。开放软件包索引使用的内存与具有开放连接的临时软件包数量(按软件包哈希分组)成正比。由于目前临时软件包的使用方式,这两个内存占用空间都很小且大小相似(如果运行许多不同的测试软件包,打开的软件包索引会更小,因为动态索引会有效地泄露这些条目)。内存占用空间方面的任何差异都应小于通过对用于提供软件包目录的数据结构进行重复数据删除而节省的内存。

工效学设计

将动态索引替换为开放软件包索引可使系统更易于理解和操作:

  • 打开的软件包索引的状态(以及 GC 的行为)仅取决于当前正在使用的软件包,而动态索引的状态取决于自启动以来发生的每次软件包解析的顺序。
  • 与动态索引不同,开放软件包索引不依赖于软件包的路径(可在软件包的 meta/package 文件中找到)。用户通常不会将软件包路径视为一个概念(他们通常会了解软件包网址的路径组成部分,但 meta/package 路径可能有所不同),而现在,GC 行为将不再依赖于此。此问题修复了以下问题:来自不同代码库但具有相同软件包路径的不相关软件包会争夺 GC 保护(通过将彼此从动态索引中逐出)。此更改还移除了对软件包路径的最后剩余依赖项之一。
  • 用户不再需要担心垃圾回收机制会删除当前正在执行的组件中的软件包。

安全注意事项

无影响。

隐私注意事项

无影响。

测试

我们对软件包解析和 GC 之间的一般交互以及 OTA 和 GC 之间的特定交互进行了广泛的测试。我们将检查这些测试,以确保它们仍然有意义且完整。

文档

我们将更新现有的 GC 文档

诊断

通过 Inspect 公开写入和打开的软件包指数。基础软件包、缓存软件包和保留的索引已包含在 pkg-cache 的检查数据中。

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

我们认为,根据要求,此解决方案可使 GC 比当前更加正确。不过,仍存在一些未知因素和缺点。

缺点

当前实现尝试保护当前未使用但预计会再次解析的临时软件包,以避免稍后重新下载 blob。建议的实现方式不具备任何此类保护措施。这不应破坏任何工作流,因为即使在当前实现中,临时解析仍需要网络访问权限才能检查代码库元数据(这意味着设备仍应能够重新下载 blob),并且 GC 很少触发,因此需要重新下载 blob 的情况也应该很少见。此外,所提出的方法不会阻止日后重新添加预测性保护。

前一个缺点带来的一个后果是,在工作流程中间触发 GC 时,必须谨慎操作,因为该工作流程依赖于多个软件包,但并未保持与这些软件包目录的开放连接。从理论上讲,目标工作流应该能够保持所有必需软件包处于打开状态,但在主机上编排的工作流可能会发现这更加困难。

与当前实现不同,当解析出不同版本的软件包(由 meta/package 中的路径标识)时,缓存软件包仍会受到保护。这意味着,在解析不同版本并触发 GC 后,缓存回退(例如,在网络不再可用时使用)仍会成功。如果非缓存版本以意外方式修改了配置文件,则会造成不良影响。这是可以接受的,因为如果未触发 GC,并且 GC 很少被触发,则目前已经可能会出现此问题。

替代方案

不再为系统更新程序提供排除在开放软件包跟踪之外的特殊 fuchsia.pkg/PackageResolver 功能,而是让系统更新程序在触发 GC 之前关闭与中间软件包的连接。

打开的软件包索引会异步更新(每当发现连接已关闭时),并且系统更新程序无法知道何时发生了这种情况。我们可以创建一个 API 来查询开放软件包索引,但目标不是让系统更新程序无条件回收中间软件包,而是让系统更新程序在唯一开放的连接是与系统更新程序的连接时回收中间软件包(考虑中间软件包也是当前系统的基本软件包的情况),并且软件包服务机制不知道谁持有连接的客户端。此外,系统更新程序已通过保留的索引手动跟踪其已解析的软件包,因此从自动跟踪中排除其解析是合理的。

不再为系统更新程序提供排除在开放软件包跟踪之外的特殊 fuchsia.pkg/PackageResolver 功能,而是继续为系统更新程序提供标准功能,并将保留的软件包排除在开放软件包跟踪之外。

此方法可能会导致运行组件的软件包被垃圾回收。 请注意以下几点:

  1. 开发者修改测试
  2. 系统会自动暂存 OTA,并且测试位于系统的缓存软件包中,因此会添加到保留的索引中
  3. 开发者运行测试,但测试软件包未添加到开放软件包索引中,因为该软件包位于保留索引中
  4. 系统自动暂存另一个 OTA(可能是因为第一次尝试失败),用于更新到不同的系统版本,该版本会从保留的索引中移除测试软件包
  5. 触发了垃圾回收,删除了正在运行的测试中的 blob

未知

打开包裹跟踪功能可保护任何已打开的包裹。某些组件可能会比我们预期的更长时间地持有软件包目录句柄。这会导致出现与开发者目前因空间不足错误而导致软件包解析失败时遇到的问题类似的问题。您需要找到(通常可以使用 k zx ch 等控制台命令轻松找到)并修复所有此类实例。这对用户设备来说不会有问题,因为在用户设备上,只有系统更新程序使用临时解析。