修复: checkpoint 参数化 + 压缩 permit 死锁降级 + 密钥归一化 + SSE CRLF/多字节

This commit is contained in:
lxy
2026-08-01 11:30:05 +08:00
parent c4ba920cf5
commit 20ec571dbb
9 changed files with 232 additions and 42 deletions
+28
View File
@@ -123,6 +123,34 @@ impl LlmConcurrency {
sema.acquire_owned().await.expect("llm per_conv semaphore closed")
}
/// 取单对话内并发 permit 的**非阻塞**版本(对齐 `acquire_per_sub_flow` 语义)。
///
/// 与 `acquire_per_conv` 同样按 conv_id 取/建 Semaphore(permits=当前 per_conv_permits),
/// 区别仅在最后一步用 `try_acquire_owned`(非阻塞):耗尽返 `None`,调用方降级,不阻塞。
///
/// **用途**:压缩(`compress_via_llm`)在 agentic loop 内被调用,loop 入口已持 1 个 per_conv
/// permit;当 `set_per_conv(1)` 后 per_conv Semaphore 仅 1 permit,loop 入口占满,压缩再
/// `acquire_per_conv`(阻塞)将永久等待 → `is_compressing` 永真 → 卡死。改用本非阻塞版本:
/// 拿不到(None)调用方提前返回降级(下轮再试),非崩溃。
///
/// - 返 `Some(permit)`:成功获取,permit 随 Drop 自动释放。
/// - 返 `None`:本对话并发已耗尽,调用方降级(非错误)。锁持有短(不含 try_acquire)。
pub async fn try_acquire_per_conv(
&self,
conv_id: &str,
) -> Option<tokio::sync::OwnedSemaphorePermit> {
let sema = {
let mut map = self.per_conv.lock().await;
let permits = self.per_conv_permits.load(std::sync::atomic::Ordering::SeqCst);
map.entry(conv_id.to_string())
.or_insert_with(|| Arc::new(Semaphore::new(permits)))
.clone()
};
// try_acquire_owned 非阻塞:Err(NoPermits) 返 None 降级,不阻塞调用方。
// 对齐 acquire_global/per_conv 的 expect 前提:Semaphore 不会 close(无 close() 调用)。
sema.try_acquire_owned().ok()
}
/// F-09 B 批5:conv 退出清理。loop 结束 + 无 pending 审批时 remove 该 conv 的 Semaphore 条目。
/// **时机由调用方判断**(agentic loop 退出点):仅在确信无后续 acquire 时调用,否则误删会致
/// 该 conv 下次 acquire 重建 Semaphore(限流计数清零,非致命,但语义偏离)。