fix(knowledge): 全量修复多 worker 元数据竞态问题
1. collection.py: create/delete/update/exists 方法开头加 _load_metadata() 2. list_collections: 去掉磁盘扫描自动补充和 stale 清理逻辑, 改为以元数据为唯一真相源,异常集合只跳过不删不改 3. manager.py: _load/_save_metadata 加 fcntl 文件锁防并发写覆盖, Windows 兼容(fcntl try/import 包裹)
This commit is contained in:
@@ -29,6 +29,11 @@
|
||||
import os
|
||||
import json
|
||||
import threading
|
||||
try:
|
||||
import fcntl
|
||||
_HAS_FCNTL = True
|
||||
except ImportError:
|
||||
_HAS_FCNTL = False
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from pathlib import Path
|
||||
import logging
|
||||
@@ -134,22 +139,36 @@ class KnowledgeBaseManager(
|
||||
logger.info(f"知识库管理器初始化完成,路径: {self.base_path},发现 {len(existing_kbs)} 个向量库: {existing_kbs}")
|
||||
|
||||
def _load_metadata(self) -> dict:
|
||||
"""加载元数据"""
|
||||
"""加载元数据(带文件锁)"""
|
||||
metadata_path = os.path.join(self.base_path, KB_METADATA_FILE)
|
||||
if os.path.exists(metadata_path):
|
||||
try:
|
||||
with open(metadata_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
|
||||
try:
|
||||
return json.load(f)
|
||||
finally:
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
except Exception as e:
|
||||
logger.error(f"加载元数据失败: {e}")
|
||||
return {"collections": {}}
|
||||
|
||||
def _save_metadata(self):
|
||||
"""保存元数据"""
|
||||
"""保存元数据(带文件锁,防止并发写入覆盖)"""
|
||||
metadata_path = os.path.join(self.base_path, KB_METADATA_FILE)
|
||||
try:
|
||||
with open(metadata_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(self._metadata, f, ensure_ascii=False, indent=2)
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
try:
|
||||
json.dump(self._metadata, f, ensure_ascii=False, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
finally:
|
||||
if _HAS_FCNTL:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
except Exception as e:
|
||||
logger.error(f"保存元数据失败: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user