修复: 压缩重入标志复位与兜底提交收口单锁
- keyword_fallback_commit 内单锁一并 set_compressing(false) + 兜底提交,消「锁外复位→再取锁提交」并发二次压缩窗口(放大楔入概率) - 复位置于 KEYWORD_FALLBACK_ENABLED 门控之前(关开关同样复位,防标志永真致压缩停摆) - 两处调用点(provider 重建失败/LLM 失败)独立复位块删除 - 验收: cargo check -p devflow EXIT 0 + cargo test -p devflow --lib 749 passed/0 failed;看板 909 收口
This commit is contained in:
@@ -218,13 +218,8 @@ pub(super) async fn maybe_auto_compress(
|
||||
"[ai] 后台压缩 provider 重建失败,降级走关键词摘要兜底(KEYWORD_FALLBACK_ENABLED={})",
|
||||
KEYWORD_FALLBACK_ENABLED,
|
||||
);
|
||||
// 单次锁内 check+mutate:复位 is_compressing(防 conv 两次锁间被删惰性重建)。
|
||||
{
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(&conv_id) {
|
||||
session.conv(&conv_id).messages.set_compressing(false);
|
||||
}
|
||||
}
|
||||
// 复位 is_compressing 收口进 keyword_fallback_commit 单锁(909①-补:
|
||||
// 原先此处单独取锁复位,与提交之间留并发二次压缩窗口)。
|
||||
keyword_fallback_commit(
|
||||
&session_arc,
|
||||
&conv_id,
|
||||
@@ -305,13 +300,8 @@ pub(super) async fn maybe_auto_compress(
|
||||
// 压缩失败不发 AiError——前端 AiError case 无条件 setStreaming(false)
|
||||
// 误判生成结束(后端 loop 实际继续),致"压缩后停止"。降级为 warn + 兜底释放
|
||||
// token + build_for_request 裁剪,loop 继续 stream_llm,用户自然看到后续回复。
|
||||
// 单次锁内 check+mutate(防 conv 在两次锁间被删致惰性重建污染新 conv)。
|
||||
{
|
||||
let mut session = session_arc.lock().await;
|
||||
if session.per_conv.contains_key(&conv_id) {
|
||||
session.conv(&conv_id).messages.set_compressing(false);
|
||||
}
|
||||
}
|
||||
// 复位 is_compressing 收口进 keyword_fallback_commit 单锁(909①-补,
|
||||
// 同 provider 重建失败路径;消锁外复位→再取锁提交的并发窗口)。
|
||||
tracing::warn!(
|
||||
conv_id = %conv_id,
|
||||
error = %e,
|
||||
@@ -339,12 +329,18 @@ pub(super) async fn maybe_auto_compress(
|
||||
/// 关键词兜底提交单点(DRY #8 收敛,2026-10-01)。
|
||||
///
|
||||
/// 抽自 [`maybe_auto_compress`] 后台 task 内的逐字双胞胎块:压缩 provider 重建失败路径
|
||||
/// 与压缩 LLM 失败路径共用「KEYWORD_FALLBACK_ENABLED 门控 → 锁内 check+mutate
|
||||
/// (标 compressed 释放 token + 关键词摘要尾部追加,非空才插)→ 成功插入打 info」。
|
||||
/// 与压缩 LLM 失败路径共用「复位 is_compressing → KEYWORD_FALLBACK_ENABLED 门控 → 锁内
|
||||
/// check+mutate(标 compressed 释放 token + 关键词摘要尾部追加,非空才插)→ 成功插入打 info」。
|
||||
/// 两条失败路径只差日志正文,经 `log_head` 参数化(拼为
|
||||
/// "[ai] {log_head}兜底(后台):关键词摘要已尾部追加(compressed 标记已扣 token)")。
|
||||
///
|
||||
/// conv 已删(per_conv 无条目)→ 直接返回不兜底(对齐原 return 语义:防惰性重建污染新 conv)。
|
||||
/// **复位重入标志收口单锁(909①-补,2026-10-10)**:`set_compressing(false)` 与兜底提交在同一
|
||||
/// 次锁内原子完成——原调用方在锁外单独取锁复位,与这里的提交之间留出窗口(期间的压缩触发读到
|
||||
/// 的仍是 false,可并发二次压缩,放大楔入概率)。注意复位在 `KEYWORD_FALLBACK_ENABLED` 门控
|
||||
/// **之前**:开关关闭时同样必须复位,否则 is_compressing 永真 → 压缩永久停摆。
|
||||
///
|
||||
/// conv 已删(per_conv 无条目)→ 直接返回(标志随 conv 销毁无需复位;对齐原 return 语义:
|
||||
/// 防惰性重建污染新 conv)。
|
||||
async fn keyword_fallback_commit(
|
||||
session_arc: &Arc<Mutex<AiSession>>,
|
||||
conv_id: &str,
|
||||
@@ -353,24 +349,28 @@ async fn keyword_fallback_commit(
|
||||
pre_tokens: u32,
|
||||
log_head: &str,
|
||||
) {
|
||||
if !KEYWORD_FALLBACK_ENABLED {
|
||||
return;
|
||||
}
|
||||
let inserted = {
|
||||
let mut session = session_arc.lock().await;
|
||||
if !session.per_conv.contains_key(conv_id) {
|
||||
return;
|
||||
}
|
||||
let conv = session.conv(conv_id);
|
||||
let _compressed = conv.messages.compress_old_messages(protect_start);
|
||||
if keyword_fallback.is_empty() {
|
||||
// 复位与提交同锁原子(909①-补):消「锁外复位→再取锁提交」之间的并发二次压缩窗口。
|
||||
conv.messages.set_compressing(false);
|
||||
if !KEYWORD_FALLBACK_ENABLED {
|
||||
// 开关关闭 → 回退原裸裁剪行为:不压缩不插摘要,仅完成复位。
|
||||
false
|
||||
} else {
|
||||
// 尾部追加(而非 insert_at(0) 头部插入):头部插入位移整条前缀字节,
|
||||
// 使 DeepSeek 等自动前缀缓存 provider 全部缓存失效(命中率掉到个位数)。
|
||||
// 尾部追加紧邻本轮内容 LLM 仍可见,前缀稳定命中率最大化。
|
||||
conv.messages.push_system_anchor(ChatMessage::system(keyword_fallback));
|
||||
true
|
||||
let _compressed = conv.messages.compress_old_messages(protect_start);
|
||||
if keyword_fallback.is_empty() {
|
||||
false
|
||||
} else {
|
||||
// 尾部追加(而非 insert_at(0) 头部插入):头部插入位移整条前缀字节,
|
||||
// 使 DeepSeek 等自动前缀缓存 provider 全部缓存失效(命中率掉到个位数)。
|
||||
// 尾部追加紧邻本轮内容 LLM 仍可见,前缀稳定命中率最大化。
|
||||
conv.messages.push_system_anchor(ChatMessage::system(keyword_fallback));
|
||||
true
|
||||
}
|
||||
}
|
||||
};
|
||||
if inserted {
|
||||
|
||||
Reference in New Issue
Block a user