修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离
- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条) - queue 加 conversationId 字段,按会话精准 drain - regenerate/editMessage 只清本会话排队消息 - newConversation 保留旧会话排队消息 - AiError 只清出错会话的队列项
This commit is contained in:
@@ -191,6 +191,112 @@ impl std::fmt::Display for InvalidTransition {
|
||||
|
||||
impl std::error::Error for InvalidTransition {}
|
||||
|
||||
// ============================================================
|
||||
// ConvStateStore — 无锁并发 ConvState 存储(session 锁重构方案 B-Phase0)
|
||||
//
|
||||
// 背景:AiSession 全局 Mutex 把 ConvState(高频读 + 敏感脏读)与 messages/pending_approvals
|
||||
// (长持锁源)同锁串行化,致 guard.reset 等 lock 竞争 800ms fallback(AiCompleted 延迟 / 工具后
|
||||
// 中断 / 第二条进队列同源根因)。本 Store 把 ConvState 提到独立 DashMap,guard.reset/new/drop
|
||||
// 直接 transition(同步无 await,不竞争 session lock),ai_is_generating 直接读(零锁竞争)。
|
||||
//
|
||||
// 设计:
|
||||
// - 基于 dashmap::DashMap<String, ConvState>(行级锁,不同 conv 不互斥,无 tokio runtime 阻塞)
|
||||
// - 全方法同步无 await(transition 内仅 copy + write enum,纳秒级)
|
||||
// - transition 经 ConvState::transition_to 守卫(复用状态机语义,非法转换拒绝)
|
||||
// - get 对不存在的 conv_id 返 Idle(惰性默认,对齐 PerConvState 新建语义)
|
||||
//
|
||||
// 方案 B 分阶段迁移完成。
|
||||
// - Phase0~1: ConvStateStore 骨架 + AppState 接入
|
||||
// - Phase2~3: 写/读侧迁移至无锁 ConvStateStore,PerConvState.conv_state 字段已删
|
||||
// - Phase4: conversation_delete 同步清理 conv_states 条目
|
||||
// ============================================================
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
/// ConvState 的无锁并发存储(方案 B 核心)。
|
||||
///
|
||||
/// 经 `Arc<ConvStateStore>` 共享(app_state.conv_states)。所有方法同步无 await,可在任意
|
||||
/// async 上下文直接调(不竞争 session lock,不阻塞 tokio runtime)。
|
||||
///
|
||||
/// 注:`transition` 用 DashMap entry 原子(get + transition_to + write 一致,无 TOCTOU 窗口)。
|
||||
pub struct ConvStateStore {
|
||||
inner: DashMap<String, ConvState>,
|
||||
}
|
||||
|
||||
impl ConvStateStore {
|
||||
/// 创建空 Store。
|
||||
pub fn new() -> Self {
|
||||
Self { inner: DashMap::new() }
|
||||
}
|
||||
|
||||
/// 读 conv_id 的 ConvState(不存在返 Idle 默认,对齐 PerConvState 新建语义)。
|
||||
pub fn get(&self, conv_id: &str) -> ConvState {
|
||||
self.inner.get(conv_id).map(|r| *r.value()).unwrap_or(ConvState::Idle)
|
||||
}
|
||||
|
||||
/// 读 conv_id 是否活跃生成态(Generating/Compressed)—— ai_is_generating 零锁读。
|
||||
pub fn is_active(&self, conv_id: &str) -> bool {
|
||||
self.get(conv_id).is_active()
|
||||
}
|
||||
|
||||
/// 读 conv_id 是否可接受新请求(Idle/Error)—— can_accept_request 零锁读。
|
||||
pub fn can_accept_request(&self, conv_id: &str) -> bool {
|
||||
self.get(conv_id).can_accept_request()
|
||||
}
|
||||
|
||||
/// 原子迁移 conv_id 的 ConvState 到 target(经 transition_to 守卫)。
|
||||
///
|
||||
/// DashMap entry 原子(get + transition + write 一致,无 TOCTOU)。不存在的 conv_id 视为
|
||||
/// Idle(对齐新建语义),Idle→target 经守卫。返回 Ok(新态) 或 Err(InvalidTransition)。
|
||||
pub fn transition(
|
||||
&self,
|
||||
conv_id: &str,
|
||||
target: ConvState,
|
||||
) -> Result<ConvState, InvalidTransition> {
|
||||
// get_mut 持写锁原子迁移(Occupied);Vacant 时 insert(Idle 起步)。
|
||||
// 注:transition 调用点(guard.new/reset/drop)同 conv 单 loop 不并发,TOCTOU 风险低;
|
||||
// 跨 conv 各自条目行级锁不互斥(对齐 DashMap 设计)。
|
||||
if let Some(mut r) = self.inner.get_mut(conv_id) {
|
||||
let cur = *r.value();
|
||||
match cur.transition_to(target) {
|
||||
Ok(ns) => {
|
||||
*r.value_mut() = ns;
|
||||
Ok(ns)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
match ConvState::Idle.transition_to(target) {
|
||||
Ok(ns) => {
|
||||
self.inner.insert(conv_id.to_string(), ns);
|
||||
Ok(ns)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除 conv_id 的条目(会话删除时同步清,防已删 conv 残留 Generating 致 id 复用脏状态)。
|
||||
pub fn remove(&self, conv_id: &str) {
|
||||
self.inner.remove(conv_id);
|
||||
}
|
||||
|
||||
/// 所有活跃生成态的 conv_id 快照(L0 握手批量 stop / 恢复生成态用)。
|
||||
pub fn active_convs(&self) -> Vec<String> {
|
||||
self.inner
|
||||
.iter()
|
||||
.filter(|r| r.value().is_active())
|
||||
.map(|r| r.key().clone())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConvStateStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单元测试(纯逻辑无 IO)
|
||||
// ============================================================
|
||||
@@ -458,4 +564,60 @@ mod tests {
|
||||
assert!(msg.contains("Idle"), "Display 应含 from: {}", msg);
|
||||
assert!(msg.contains("Stopping"), "Display 应含 to: {}", msg);
|
||||
}
|
||||
|
||||
// ---- ConvStateStore(方案 B-Phase0,无锁并发存储)----
|
||||
|
||||
#[test]
|
||||
fn test_store_get_default_idle() {
|
||||
let s = ConvStateStore::new();
|
||||
assert_eq!(s.get("conv-1"), ConvState::Idle, "不存在 conv 应返 Idle 默认");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_transition_occupied() {
|
||||
let s = ConvStateStore::new();
|
||||
s.transition("conv-1", ConvState::Generating).unwrap();
|
||||
assert_eq!(s.get("conv-1"), ConvState::Generating);
|
||||
s.transition("conv-1", ConvState::Idle).unwrap();
|
||||
assert_eq!(s.get("conv-1"), ConvState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_transition_guard_rejects() {
|
||||
let s = ConvStateStore::new();
|
||||
s.transition("conv-1", ConvState::Generating).unwrap();
|
||||
// Generating → Stopping 合法
|
||||
s.transition("conv-1", ConvState::Stopping).unwrap();
|
||||
// Stopping → Generating 非法(须先回 Idle 再起)
|
||||
assert!(s.transition("conv-1", ConvState::Generating).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_remove() {
|
||||
let s = ConvStateStore::new();
|
||||
s.transition("conv-1", ConvState::Generating).unwrap();
|
||||
s.remove("conv-1");
|
||||
assert_eq!(s.get("conv-1"), ConvState::Idle, "remove 后应返 Idle 默认");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_is_active_and_can_accept() {
|
||||
let s = ConvStateStore::new();
|
||||
assert!(!s.is_active("conv-1"), "Idle 不活跃");
|
||||
assert!(s.can_accept_request("conv-1"), "Idle 可接");
|
||||
s.transition("conv-1", ConvState::Generating).unwrap();
|
||||
assert!(s.is_active("conv-1"), "Generating 活跃");
|
||||
assert!(!s.can_accept_request("conv-1"), "Generating 不可接");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_active_convs() {
|
||||
let s = ConvStateStore::new();
|
||||
s.transition("a", ConvState::Generating).unwrap();
|
||||
s.transition("b", ConvState::Idle).unwrap();
|
||||
s.transition("c", ConvState::Generating).unwrap();
|
||||
let mut active = s.active_convs();
|
||||
active.sort();
|
||||
assert_eq!(active, vec!["a".to_string(), "c".to_string()], "仅活跃 conv");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user