Session 内部并不是“一个大锁保护所有东西”。源码把长期服务句柄、跨 Turn 值状态和当前 Turn 状态拆成三层;这个拆分决定哪些对象可以无锁共享、哪些更新必须原子发生、哪些等待者在 Turn 结束时必须被清空。
三层所有权
| 层 | 主要类型 | 生命周期 | 典型内容 |
|---|---|---|---|
| 长期服务 | SessionServices | Session 全程 | 模型、MCP、Shell、Rollout、Plugins、Network Proxy、AgentControl |
| 跨 Turn 状态 | SessionState | Session 全程、持续修改 | SessionConfiguration、历史、Token、限流、连接器选择 |
| 当前 Turn 状态 | ActiveTurn / TurnState | 一次活动 Turn | RunningTask、审批 waiter、pending input、临时权限、工具计数 |
SessionServices 里的对象通常已经通过 Arc、ArcSwap 或内部锁实现并发控制,因此外层不再用 SessionState 的互斥锁包住网络调用。例如 ModelClient、MCP runtime、UnifiedExecProcessManager 和 Rollout recorder 都有自己的生命周期。
SessionState 则把需要一致观察的一组跨 Turn 值放在同一个 Tokio Mutex 后面。最典型的是“应用本次 settings update,同时生成 TurnContext”:若模型已经换成 B,而权限仍从旧配置 A 读取,新的 Turn 会得到撕裂快照。单锁让配置变换和快照来源形成一个原子事务。
class Session:
services: SessionServices # 长期句柄
state: Mutex[SessionState] # 跨 Turn 值
active_turn: Mutex[Optional[ActiveTurn]] # 当前 Turn 容器
input_queue: InputQueue # Session + Turn 两级输入
async def begin_turn(update):
async with state.lock() as s:
next_config = apply_update(s.session_configuration, update)
s.session_configuration = next_config
snapshot_source = next_config.clone()
return await build_turn_context(snapshot_source)
SessionState 为什么不装服务对象
SessionState 包含 ContextManager、Token/Ratelimit、额外上下文、PreviousTurnSettings、自动压缩窗口、启动预热、时间提醒、连接器选择、环境选择配置和已授予权限等。这些都是“当前会话事实”,恢复、换 Turn 或配置更新会改变。
如果把网络服务也塞进同一个 Mutex,读取历史时可能持锁等待 MCP 或 HTTP,Submission 主循环随之无法响应 Interrupt。相反,如果把 SessionConfiguration 拆成许多独立锁,本次 Turn 又可能组合出不同时间点的模型、权限和环境选择。当前边界是在一致性和并发性之间做的折中。
ActiveTurn 再拆执行对象和领域状态
ActiveTurn 同时保存可选 RunningTask 与共享 TurnState。RunningTask 是执行壳:Task 策略、CancellationToken、AbortOnDropHandle、TurnContext、Notify 和执行计时器。TurnState 则保存工具执行过程中会被 Submission handler 回填的 oneshot sender、pending input、MCP 审批元数据和临时权限。
class ActiveTurn:
task: Optional[RunningTask]
turn_state: Arc[Mutex[TurnState]]
class TurnState:
pending_input: list[TurnInput]
approval_waiters: dict[CallId, OneShotSender]
permission_waiters: dict[CallId, OneShotSender]
dynamic_tool_waiters: dict[CallId, OneShotSender]
granted_permissions: set[Permission]
tool_calls: int
Task 正常返回时,完成处理先从 ActiveTurn 取走 task,但仍保留 TurnState 来统计和处理边界输入;发终态后才用 Arc 指针身份确认并清空整个 ActiveTurn。取消时则先拿走 ActiveTurn,阻断新输入,再通知任务退出,最后清 pending waiters。这个顺序体现了“执行所有权”和“领域状态所有权”是两个阶段。
锁边界的规则
- 不在 SessionState 锁内做长网络 I/O;先复制配置,再在锁外刷新服务。
- ActiveTurn 与 TurnState 需要联合判断时保持固定加锁顺序,避免 steer 与完成竞态。
- 跨 Turn 的授权不能误放进 TurnState;仅本轮批准则不能写入永久配置。
- Features 在 Session 建立后视为不变量,避免每个热路径反复加锁。
评论
登录后即可评论