Skip to content

fix(mac): 复用连接认证以停止后台内存增长 - #12

Merged
xiaoland merged 2 commits into
mainfrom
hotfix/mac-keychain-memory
Nov 12, 2026
Merged

xiaoland merged 2 commits into
mainfrom
hotfix/mac-keychain-memory

Conversation

@xiaoland

@xiaoland xiaoland commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

目的与行为

Mac 后台续租、目录刷新与 Capture 重试曾反复向 file-based Keychain 取密码。本机合成凭据对照复现每次成功取密约增加 16 KiB;原进程连续运行六天后 footprint 约 581 MB,其中约 528 MB 为匿名 VM。

认证现在归 PeerDatabase 的已授权会话所有。没有会话时取得一次凭据;普通查询、重试和续租直接复用 client,不再查询 Keychain、序列化属性或协调全局 generation。.password 与 .keychain 只提供冷启动凭据;明确的 authorize/invalidate 更新或撤销会话,旧引用不能回切密码、用户名或 TLS 配置。

Host、Capture 与 Local Files 共用一个 PeerHostContext。连接编辑和 Capture 待办改密都通过该 owner 更新认证;留空密码保存连接会重新读取 Keychain,失败则结束该会话。Keychain 外部锁定、删项或修改不会自动撤销既有数据库会话。

首次 Capture 先准备认证,再持久化可能发送的状态。凭据不可用时保持 attempts == 0,保留原 UUID、材料和目标;取消文案仍为“尚未发送”,恢复认证后才真正增加尝试次数。

范围:Mac App、PeerCore、随包 Capture 与 Local Files;本机候选为 0.1.0 build 21。

审查处理

  • 首次 Capture 状态:将认证准备放到 markDispatch 前,覆盖失败、取消、停止和重试。
  • 会话语义与来源混用:取消持续 Keychain 撤销检查的前提;普通调用不更换既有授权,只有 owner 的明确操作可以更新。
  • revision 成本与全局 generation:删除这两套机制,收拢重复构造的凭据/设置入口,不新增 provider 框架。

保留现有 file-based Keychain,以继续读取已安装应用的凭据。本次问题由不必要的重复取密触发,不把更换持久化位置设为修复前置。Data Protection Keychain 有不同的访问控制和宿主签名要求,迁移需单独安排。Apple TN3137

验证

  • bash scripts/check.sh 通过:70 项测试中 66 项执行通过,4 项数据库依赖场景按原环境条件跳过;格式、lint 和脚本检查通过。
  • SWIFT_JOBS=2 bash scripts/check-core.sh 通过:独立消费者仅链接 Core,不启动 App/UI。
  • 独立消费者在锁定的临时 Keychain 下完成 500 次会话访问,仍复用同一 client,内存增加 64 KiB;显式撤销后的冷读取失败、重新授权、旧引用及同密码复用检查通过。
  • Capture 真实凭据不可用回归证明首次失败不增加 attempts、不会进入 dispatch/recovery,身份和材料保持;定向审查未发现待追加语义修复。
  • 新 head 45e5669 的 CI 全部通过;签名 universal 构建通过,arm64/x86_64 双架构与签名验证通过,指定要求与原开发包一致。
  • 本机已安装并启动 0.1.0 build 21,90 秒 footprint 采样为 33 → 36 → 36 → 36 MB。该短时观测不代表六天运行验证;持续取密热点由上述隔离回归覆盖。
  • 当前系统没有活动显示器,应用窗口可枚举但无法读取 accessibility element,截图也返回 CoreGraphics 无活动显示器;本轮未完成图形交互验收。

风险、回退与交付

采用数据库会话授权;Keychain 的锁定或删项只影响下一次取密。需要强制核验时,可重新保存连接或重启应用。README、Core README 与 Local Files README 已同步。

沿用现有 Bundle ID 和开发证书,本机配置、材料与数据库 schema 不迁移;原路径旁保留 .InKCre-before-build21.app 供回退。签名应用、DMG、dSYM、脚本与验证记录保存在本机候选制品 mac-keychain-memory-build21-45e5669。没有父/子堆叠 PR。

实现、构建、安装与 CI 已完成,待复核 Capture 状态边界与会话语义。图形交互验收受上述本机显示环境限制。

- 由连接池统一持有 Keychain 认证,后台传递凭据引用
- 保存和重新授权时更新认证,并保留锁定、删项与多连接恢复
- 覆盖真实 Keychain 回归,将本机构建更新到 build 20
@xiaoland
xiaoland marked this pull request as ready for review November 12, 2026 09:16

@xiaoland xiaoland left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已完成对当前 head 395800e 的功能与实现审查。解决重复读取 file-based Keychain 凭据导致长期运行内存增长的方向合理:由 PeerDatabase 管理认证复用,Capture / LocalFiles 只传递凭据来源,不要求推翻该方案。

合并前建议修复以下两处行为问题(详见行内评论):

  1. 首次 Capture 在实际验证 Keychain 凭据前已 markDispatch,失败时会虚增 attempts 并进入“可能已发送”的恢复语义。
  2. 对同一 target 混用 .keychain(store) 与 .password(...) 时,池复用会覆盖此前的认证来源,导致续租可以绕过声明的 Keychain 锁定/删除检查。

请优先用针对性的回归测试验证原有行为与跨入口授权约束。暂不建议在此次 hotfix 内增加通用凭据框架或扩大重构范围。

Copy link
Copy Markdown
Member Author

补充设计审查:先确认认证会话语义,再决定需要多少技术机制

这次内存增长可能暴露了凭据生命周期与业务调用生命周期混淆:后台续租和 LocalFiles 刷新都约每 30 秒执行,旧实现反复向 file-based Keychain 取密;PR 所述每次约 16 KiB、六天约 521 MiB 的数量级与这些周期性调用相符(仅为归因线索,不等于严格证明)。既有 PostgresClient 已持有认证配置,业务操作不应反复取密。让连接池拥有认证会话是正确方向。

但新方案又引入了每次访问的 Keychain revision/锁定/删项检查、全属性序列化、进程级 generation 协调等。这些机制究竟由哪条产品需求驱动,还是为了满足未经充分论证的技术约束? 希望本 PR 先回到产品语义,而不是继续叠加兼容和状态机制。

建议首先区分:

  1. 真正需要的行为: 安全持久化密码;后台不弹出授权 UI;首次获取认证或明确需要重新授权时,Keychain 不可用应有可操作的错误;应用内主动改密/显式撤销授权后有确定的失效路径;Capture 的 dispatch/recovery 不变量保持正确。
  2. 需要重新论证的行为: 已经授权且运行中的数据库会话,是否必须在每次业务操作时检查 Keychain 锁定/项目存在/所有元数据?外部锁定或删项是否必须立即终止既有 PostgreSQL 会话?Keychain 禁止下一次取密,并不天然等于已建立的数据库会话需要立即撤销。 即使此前将“后台检查锁定/删项”写成了约定,也希望现在重新确认其真实用户场景及安全理由。
  3. 所有权问题: PeerHostContext 与 CaptureModel 各自创建同一 service 的 CredentialStore,全局 generation 似乎用于补偿会话/凭据更新所有权分散。请审视能否由拥有连接池的会话所有者管理“应用内变更 → 认证失效”,而不是默认需要跨实例全局协调。不要为此再新造一个大型通用 Credential Provider 框架。
  4. 底层选型(非本次强制迁移): 简要核对 legacy file-based Keychain 相对 Data Protection Keychain 的实际理由,如签名、兼容性和后台访问。若确有需要就保留,不应把替换 Keychain 设为此次 hotfix 的前置条件。

我倾向的简化目标是:Keychain 用于持久化和必要时取得认证材料;PeerDatabase 持有已授权会话;正常会话中的普通数据库操作不再访问 Keychain;应用内主动改密或显式撤销时确定性失效;真正需要重新取密且失败时才提示处理。 若确实希望“锁定 Keychain 即停止已有后台会话”,请给出用户场景,再保留对应检查。

请在本 PR 内结合上述语义重新审视实现和验收,优先删除不必要的约束与代码,而不是扩大抽象。保留内存修复已有证据,并验证:长时间后台操作不重复取密/内存不线性增长、主动改密正确生效、后台不弹授权窗口、Capture 状态不虚增,更新 README 与测试。

备注:之前关于 password/keychain 混用绕过锁定检查的行内 P2,是以现有 PR 将持续锁定检查视为既定要求为前提指出的内部不一致。若产品语义最终简化为会话授权,应相应调整该评论的验收目标,而不是为满足旧约束继续堆积技术逻辑。

@xiaoland xiaoland left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

补充两处与 PR Conversation 中的产品语义审查直接相关的实现锚点。这里要求先核对必要性,再决定删减方案;不要求为本 hotfix 新增架构。

- 移除普通操作的 Keychain revision 检查与全局 generation
- 通过共享 HostContext 明确更新和撤销数据库认证会话
- 首次 Capture 在标记发送前准备认证,失败保留零次尝试
- 补充旧引用、重新授权及 Capture 恢复回归,更新 build 21
@xiaoland
xiaoland merged commit 5c490c8 into main Nov 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant