Skip to content

fix(server): add bounded outbound client cache lifecycle#294

Merged
liujuanjuan1984 merged 2 commits intomainfrom
eval/issue-287
Mar 23, 2026
Merged

fix(server): add bounded outbound client cache lifecycle#294
liujuanjuan1984 merged 2 commits intomainfrom
eval/issue-287

Conversation

@liujuanjuan1984
Copy link
Collaborator

概述

本 PR 解决 A2AClientManager outbound client 缓存无界增长的问题,引入受控缓存与显式借用生命周期,避免在长期高频调用或多 peer 交互时仅增不减地持有 A2AClient / httpx.AsyncClient

按模块说明

src/opencode_a2a/config.py

  • 新增 A2A_CLIENT_CACHE_TTL_SECONDS
  • 新增 A2A_CLIENT_CACHE_MAXSIZE

src/opencode_a2a/server/application.py

  • A2AClientManager 从无界 dict[str, A2AClient] 调整为受控缓存
  • 为缓存条目补充 last_used / expires_at / pending_eviction
  • 新增 borrow_client(),统一管理借用前后状态刷新与回收
  • 逐出策略支持 TTL、LRU、deferred eviction
  • 当 busy client 占位导致超限时允许临时超限,待释放后优先回收 pending 条目

src/opencode_a2a/client/client.py

  • 新增 _active_requestsis_busy()
  • send_message / get_task / cancel_task / resubscribe_task 周围统一记录 in-flight usage
  • 让 manager 能判断是否安全关闭底层传输资源

src/opencode_a2a/execution/executor.py

  • a2a_callget_client() 切到 async with mgr.borrow_client(url)
  • 让 outbound client 生命周期显式收口到 manager

测试

  • 新增 tests/server/test_a2a_client_manager.py
    • 覆盖 LRU idle eviction
    • 覆盖 busy client deferred eviction
    • 覆盖 TTL expiry eviction
  • 更新 tests/server/test_transport_contract.py
    • 断言新配置透传到 A2AClientManager
  • 更新 tests/execution/test_opencode_agent_session_binding.py
    • 对齐 borrow_client() 接口

验证

  • uv run pre-commit run --all-files
  • uv run pytest
  • 339 passed
  • coverage 91.08%

关联

Closes #287

@liujuanjuan1984
Copy link
Collaborator Author

本轮基于 PR diff 做了代码审查,结论如下。

  1. 总体结论
  • 本 PR 的改动方向合理,能够更稳健地解决 #287 想处理的“outbound client 缓存无界增长”问题。
  • 这次没有停留在“给 dict 加 TTL/LRU”的局部修补,而是同时补上了 borrow 生命周期和 busy 保护,方案与当前代码约束是匹配的。
  • 当前未发现阻塞合并的问题。
  1. 已解决的关键风险
  • 避免直接关闭正在使用中的 client:通过 A2AClient.is_busy() 与 manager 的 deferred eviction,避免把仍在流式消费中的底层 httpx.AsyncClient 直接关掉。
  • 避免新借出的实例被过早逐出:当 busy client 占位导致超限时,允许临时超限,等待 pending 条目释放后再回收,策略上比“立刻赶走刚创建的新 client”更合理。
  • 配置面已完整打通:TTL / maxsize 已从 settings 透传到 manager,并有测试覆盖。
  1. 剩余风险与边界
  • maxsize 当前针对“空闲缓存容量”而不是“绝对并发上限”。如果同时存在大量不同 peer 且都处于 in-flight 状态,缓存条目仍可能临时超过上限。这是为了保证不错误关闭正在使用中的连接,属于当前实现下可接受的取舍,不构成阻塞问题。
  • 当前 cache key 仍只使用 URL。按现有实现这是准确的,因为 outbound bearer token / transport preference 来自全局 settings;如果未来引入按请求变化的 auth/header 语义,cache key 需要同步扩展。
  1. PR 与 issue 关系
  • Closes #287 是准确的。
  • 当前没有发现必须一并绑定的其它 related/closes issue。

@liujuanjuan1984
Copy link
Collaborator Author

补充修复了上一轮 reviewer 视角下发现的两个问题:

  1. 同 URL 的已过期缓存条目现在会在借用前重建,而不是被 protected_keys 保护后直接刷新复用。
  2. A2AClientManager 现在维护 manager 侧 borrow_count,逐出判断不再只依赖 A2AClient.is_busy();这样即使在“borrow 已完成、实际请求尚未开始”的窗口内,也不会把刚借出的 client 提前逐出。

补充测试:

  • 覆盖 borrowed-but-not-busy 的并发保护场景
  • 覆盖同 URL 过期后重建 client 的场景

验证:

  • uv run pre-commit run --all-files
  • uv run pytest
  • 341 passed
  • coverage 91.08%

@liujuanjuan1984 liujuanjuan1984 marked this pull request as ready for review March 23, 2026 08:50
@liujuanjuan1984 liujuanjuan1984 merged commit 8eeca83 into main Mar 23, 2026
3 checks passed
@liujuanjuan1984 liujuanjuan1984 deleted the eval/issue-287 branch March 23, 2026 08:51
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.

[bug] 为 A2AClientManager 的 outbound client 缓存引入容量/回收策略

1 participant