修复: DeepSeek 400 全量扫描 + 队列 per-conv 隔离
- openai_compat: 扫描所有 assistant 消息剥离 orphan tool_calls(原仅查末条) - queue 加 conversationId 字段,按会话精准 drain - regenerate/editMessage 只清本会话排队消息 - newConversation 保留旧会话排队消息 - AiError 只清出错会话的队列项
This commit is contained in:
@@ -287,16 +287,23 @@ type SkillsGuard = RwLockReadGuard<'static, Option<Vec<SkillInfo>>>;
|
||||
///
|
||||
/// 返 `RwLockReadGuard<Option<Vec<SkillInfo>>>`,调用方解 `*guard` 得 `&Vec<SkillInfo>`。
|
||||
/// 懒初始化走双检锁:先读锁查 Some(快),None 时释放 → 扫盘 → 写锁填回 → 读锁重取。
|
||||
fn skills_lock() -> SkillsGuard {
|
||||
// 快路径:读锁命中
|
||||
///
|
||||
/// P1-260617-3:`scan_skills` 同步递归 `fs::read_dir` + `read_to_string`(plugins/marketplaces
|
||||
/// 多层嵌套,Windows 文件多时同步阻塞 tokio runtime)。本函数改 async,慢路径扫盘包
|
||||
/// `spawn_blocking` 隔离(对齐 commands/project.rs detect_stack 模式)。快路径(读锁命中)仍同步无 fs。
|
||||
async fn skills_lock_async() -> SkillsGuard {
|
||||
// 快路径:读锁命中(无 fs,纯内存)
|
||||
{
|
||||
let g = RwLock::read(&SKILLS).expect("SKILLS poisoned");
|
||||
if g.is_some() {
|
||||
return g;
|
||||
}
|
||||
}
|
||||
// 慢路径:扫盘 + 写锁填回
|
||||
let scanned = scan_skills().skills;
|
||||
// 慢路径:扫盘(spawn_blocking 隔离同步 fs 递归,防阻塞 tokio runtime)
|
||||
let scanned = tokio::task::spawn_blocking(scan_skills)
|
||||
.await
|
||||
.map(|res| res.skills)
|
||||
.unwrap_or_default();
|
||||
{
|
||||
let mut g = RwLock::write(&SKILLS).expect("SKILLS poisoned");
|
||||
// 另一线程可能已填,二次检查(双检锁)
|
||||
@@ -314,8 +321,11 @@ fn skills_lock() -> SkillsGuard {
|
||||
///
|
||||
/// 替代原 `OnceLock::get_or_init` 路径:返 owned `Vec<SkillInfo>`(clone),
|
||||
/// 因 RwLock 不能返 `&'static`。调用方(config.rs:30 / read_skill_content_stripped)已同步适配。
|
||||
pub(crate) fn skills_cached() -> Vec<SkillInfo> {
|
||||
let g = skills_lock();
|
||||
///
|
||||
/// P1-260617-3:改 async,慢路径(首次/重扫)走 `skills_lock_async` → spawn_blocking
|
||||
/// 隔离同步 fs 防阻塞 tokio runtime(Tauri 单线程 runtime)。快路径(读锁命中)无 fs。
|
||||
pub(crate) async fn skills_cached() -> Vec<SkillInfo> {
|
||||
let g = skills_lock_async().await;
|
||||
g.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -332,11 +342,25 @@ pub(crate) fn invalidate_skills() {
|
||||
///
|
||||
/// 核心设计6:注入用正文,避免 YAML 头噪声污染 system prompt。
|
||||
/// 缓存未命中返 None;文件读失败返 None。
|
||||
pub(crate) fn read_skill_content_stripped(name: &str) -> Option<String> {
|
||||
let g = skills_lock();
|
||||
let skills = g.as_ref()?;
|
||||
let info = skills.iter().find(|s| s.name == name)?;
|
||||
let md = fs::read_to_string(&info.path).ok()?;
|
||||
///
|
||||
/// P1-260617-3:改 async,缓存懒初始化(可能触发扫盘)走 spawn_blocking 防阻塞 runtime。
|
||||
///
|
||||
/// 注:guard(std::sync::RwLockReadGuard 非 Send)必须在 spawn_blocking 的 .await 前 drop,
|
||||
/// 否则非 Send 跨 await 点致 future 不 Send(MentionResolver 要求 Send)。guard 用 { } 限作用域。
|
||||
pub(crate) async fn read_skill_content_stripped(name: String) -> Option<String> {
|
||||
// 在作用域内取 path 后立即 drop guard,避免非 Send guard 跨 spawn_blocking await 点
|
||||
let path: String = {
|
||||
let g = skills_lock_async().await;
|
||||
let skills = g.as_ref()?;
|
||||
let info = skills.iter().find(|s| s.name == name)?;
|
||||
info.path.clone()
|
||||
};
|
||||
// 单文件读取仍是同步 fs,但只一次小读;持续阻塞风险远低于扫盘递归。
|
||||
// 仍包 spawn_blocking 与慢路径一致(对齐 detect_stack:防 Windows fs 调度慢)。
|
||||
let md = tokio::task::spawn_blocking(move || fs::read_to_string(&path))
|
||||
.await
|
||||
.ok()?
|
||||
.ok()?;
|
||||
Some(strip_frontmatter(&md))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user