mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-12 20:19:51 +08:00
refactor(ws): 用 ResponsesTurnState 收敛连接 turn 状态
评审第 2 条:BoundResponsesConnection 用 response_in_flight、active_turn、
active_response_create 三个可独立变化的字段编码同一件事,8 种组合里只有 3 种
合法,非法组合只能靠调用点的 if 和「记得同时改另外两个字段」来避免。
三字段合并为一个 ResponsesTurnState:
Idle 没有进行中的 logical turn
Responding { logical, attempt } logical 与 attempt 必须同时存在
Replanning { logical } attempt 已取走去结算/重绑,logical 仍在
Replanning 不是新概念:配额透明重试期间现状就处于这个状态,只是靠
Option::take 意外得到。转换只能走 begin / detach_attempt / resume / end,
response_in_flight 与「是否接受新 response.create」都由变体推导。
由此消除的运行时不变量(原来全靠调用点自觉):
- 有 attempt 必有 logical turn
- response_in_flight 与 attempt 同生共死(原来 client 写失败后
active_turn=None 而 response_in_flight 仍为 true)
- logical turn 结束时必须清 attempt:原来 `active_response_create = None`
在 connection.rs 里手写 13 处,漏一处就残留;现在只有 end() 一个出口
- 上游绑定返回的连接不再自带 response_in_flight=true 的半成品状态
同时删除 update_response_in_flight:Started 帧把已经是 true 的字段再设一次,
Close 帧因为没有解析出的 frame 而根本不触发,是纯冗余写;它在 Idle 态收到
Started 帧时还会把 response_in_flight 置真,从而永久阻塞后续 response.create。
行为等价。ActiveResponsesWebSocketRequest 改名 LogicalTurn 并随状态机移入
新的 turn_state.rs;状态机对 attempt 类型泛型化,测试用轻量替身驱动同一套
转换逻辑,无需 AppState 或真实 socket。
This commit is contained in:
@@ -4,14 +4,12 @@
|
||||
//! connection may survive many `response.create` turns, while the turn
|
||||
//! lifecycle and upstream binding are replaced independently.
|
||||
|
||||
use serde_json::Value;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::adapter::{ResponsesWebSocketDrainDirective, ResponsesWebSocketProtocolAdapter};
|
||||
use super::binding::UpstreamBindingIdentity;
|
||||
use super::lifecycle::ActiveResponsesWebSocketTurn;
|
||||
use super::request::response_create_has_previous_response_id;
|
||||
use super::turn_state::ResponsesTurnState;
|
||||
use crate::ai_serving::{AiExecutionDecision, ResponsesWebSocketBodyNormalization};
|
||||
|
||||
const EXHAUSTED_KEY_EXCLUSION_FALLBACK_SECONDS: u64 = 300;
|
||||
@@ -22,15 +20,14 @@ pub(super) struct BoundResponsesConnection {
|
||||
pub(super) adapter: &'static dyn ResponsesWebSocketProtocolAdapter,
|
||||
pub(super) client_model: String,
|
||||
pub(super) provider_model: String,
|
||||
pub(super) response_in_flight: bool,
|
||||
pub(super) decision_template: AiExecutionDecision,
|
||||
/// Reproduces this binding's provider-body normalization for continuation
|
||||
/// turns, which must not re-enter the planner. Replaced whenever the
|
||||
/// binding or its decision is replaced.
|
||||
pub(super) body_normalization: ResponsesWebSocketBodyNormalization,
|
||||
pub(super) binding_identity: UpstreamBindingIdentity,
|
||||
pub(super) active_turn: Option<ActiveResponsesWebSocketTurn>,
|
||||
pub(super) active_response_create: Option<ActiveResponsesWebSocketRequest>,
|
||||
/// 这条连接上「有没有正在进行的 logical turn」的唯一事实来源。
|
||||
pub(super) turn_state: ResponsesTurnState,
|
||||
pub(super) next_turn_index: u64,
|
||||
pub(super) upstream_response_headers: BTreeMap<String, String>,
|
||||
pub(super) pending_adapter_drain: Option<ResponsesWebSocketDrainDirective>,
|
||||
@@ -101,41 +98,3 @@ impl ExhaustedResponsesWebSocketExclusions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ActiveResponsesWebSocketRequest {
|
||||
pub(super) client_event: Value,
|
||||
pub(super) turn_index: u64,
|
||||
pub(super) logical_turn_id: String,
|
||||
pub(super) turn_attempt: u32,
|
||||
pub(super) retry_attempted: bool,
|
||||
pub(super) retry_unsafe_reason: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ActiveResponsesWebSocketRequest {
|
||||
pub(super) fn new(client_event: Value, turn_index: u64, logical_turn_id: String) -> Self {
|
||||
Self {
|
||||
client_event,
|
||||
turn_index,
|
||||
logical_turn_id,
|
||||
turn_attempt: 1,
|
||||
retry_attempted: false,
|
||||
retry_unsafe_reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn quota_retry_block_reason(&self) -> Option<&'static str> {
|
||||
if self.retry_attempted {
|
||||
Some("quota_retry_already_attempted")
|
||||
} else if let Some(reason) = self.retry_unsafe_reason {
|
||||
Some(reason)
|
||||
} else if response_create_has_previous_response_id(&self.client_event) {
|
||||
Some("previous_response_id")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_retry_unsafe(&mut self, reason: &'static str) {
|
||||
self.retry_unsafe_reason.get_or_insert(reason);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user