feat: 优化轮询机制(重试、清理、错误提示)
Phase 3 轮询优化: 1. 添加重试机制(task.ts) - 新增 pollingErrorCount 状态字段 - 添加 5 秒超时(AbortSignal.timeout) - 连续失败 5 次后停止轮询并通知用户 - 成功后重置错误计数 2. 改进错误提示(taskPolling.ts) - 根据 error_type 提供友好提示 - 支持 api_error、timeout、quota_exceeded 等类型 - 延长错误提示显示时间(8秒) 3. 添加任务清理机制(tasks.py) - 新增 cleanup_old_tasks() 方法 - 清理超过 1 小时的已完成任务 - 启动后台定时器(每 10 分钟清理一次) - 减少内存占用,支持长期运行 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
de82a37958
commit
8d6c59deed
@ -75,6 +75,22 @@ class TaskManager:
|
||||
self._tasks: Dict[str, TaskRecord] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def cleanup_old_tasks(self, max_age_seconds: int = 3600) -> int:
|
||||
"""清理超过指定时间的已完成任务"""
|
||||
now = time.time()
|
||||
with self._lock:
|
||||
to_remove = []
|
||||
for task_id, rec in self._tasks.items():
|
||||
if rec.status in {"succeeded", "failed", "canceled"}:
|
||||
age = now - rec.updated_at
|
||||
if age > max_age_seconds:
|
||||
to_remove.append(task_id)
|
||||
|
||||
for task_id in to_remove:
|
||||
del self._tasks[task_id]
|
||||
|
||||
return len(to_remove)
|
||||
|
||||
# ---- public APIs ----
|
||||
def create_chat_task(
|
||||
self,
|
||||
@ -274,6 +290,27 @@ task_manager = TaskManager()
|
||||
tasks_bp = Blueprint("tasks", __name__)
|
||||
|
||||
|
||||
def start_task_cleanup_scheduler():
|
||||
"""启动任务清理定时器"""
|
||||
def cleanup_loop():
|
||||
while True:
|
||||
try:
|
||||
count = task_manager.cleanup_old_tasks(3600)
|
||||
if count > 0:
|
||||
debug_log(f"[Task] 清理了 {count} 个旧任务")
|
||||
except Exception as e:
|
||||
debug_log(f"[Task] 清理任务失败: {e}")
|
||||
time.sleep(600) # 每 10 分钟
|
||||
|
||||
thread = threading.Thread(target=cleanup_loop, daemon=True, name="TaskCleanup")
|
||||
thread.start()
|
||||
debug_log("[Task] 任务清理定时器已启动")
|
||||
|
||||
|
||||
# 启动清理定时器
|
||||
start_task_cleanup_scheduler()
|
||||
|
||||
|
||||
@tasks_bp.route("/api/tasks", methods=["GET"])
|
||||
@api_login_required
|
||||
def list_tasks_api():
|
||||
|
||||
@ -609,12 +609,33 @@ export const taskPollingMethods = {
|
||||
},
|
||||
|
||||
handleTaskError(data: any) {
|
||||
const errorMessage = data.message || '未知错误';
|
||||
const errorType = data.error_type || 'unknown';
|
||||
|
||||
let title = '任务执行失败';
|
||||
let message = errorMessage;
|
||||
|
||||
// 根据错误类型提供友好提示
|
||||
if (errorType === 'api_error') {
|
||||
title = 'API 调用失败';
|
||||
message = `模型服务异常:${errorMessage}`;
|
||||
} else if (errorType === 'timeout') {
|
||||
title = '任务超时';
|
||||
message = '任务执行时间过长,已自动停止';
|
||||
} else if (errorType === 'quota_exceeded') {
|
||||
title = '配额不足';
|
||||
message = '您的使用配额已用尽';
|
||||
}
|
||||
|
||||
console.error('[TaskPolling] 任务错误:', data.message);
|
||||
this.uiPushToast({
|
||||
title: '任务执行失败',
|
||||
message: data.message || '未知错误',
|
||||
type: 'error'
|
||||
title,
|
||||
message,
|
||||
type: 'error',
|
||||
duration: 8000
|
||||
});
|
||||
|
||||
// 清理状态
|
||||
this.streamingMessage = false;
|
||||
this.taskInProgress = false;
|
||||
this.stopRequested = false;
|
||||
|
||||
@ -10,6 +10,7 @@ export const useTaskStore = defineStore('task', {
|
||||
taskStatus: 'idle' as 'idle' | 'running' | 'succeeded' | 'failed' | 'canceled',
|
||||
isPolling: false,
|
||||
pollingError: null as string | null,
|
||||
pollingErrorCount: 0, // 新增错误计数
|
||||
taskCreatedAt: null as number | null,
|
||||
taskUpdatedAt: null as number | null,
|
||||
}),
|
||||
@ -74,11 +75,12 @@ export const useTaskStore = defineStore('task', {
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/tasks/${this.currentTaskId}?from=${this.lastEventIndex}`
|
||||
`/api/tasks/${this.currentTaskId}?from=${this.lastEventIndex}`,
|
||||
{ signal: AbortSignal.timeout(5000) } // 5秒超时
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('轮询任务失败');
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
@ -113,14 +115,27 @@ export const useTaskStore = defineStore('task', {
|
||||
this.stopPolling();
|
||||
}
|
||||
|
||||
// 重置错误计数
|
||||
this.pollingError = null;
|
||||
this.pollingErrorCount = 0;
|
||||
} catch (error) {
|
||||
console.error('[Task] 轮询失败:', error);
|
||||
this.pollingErrorCount++;
|
||||
this.pollingError = error.message;
|
||||
|
||||
// 连续失败3次后停止轮询
|
||||
if (this.pollingError) {
|
||||
// 可以在这里添加重试逻辑
|
||||
console.error('[Task] 轮询失败:', error);
|
||||
|
||||
// 连续失败 5 次后停止
|
||||
if (this.pollingErrorCount >= 5) {
|
||||
this.stopPolling();
|
||||
// 通知用户
|
||||
if ((window as any).__vueApp?.uiPushToast) {
|
||||
(window as any).__vueApp.uiPushToast({
|
||||
title: '轮询失败',
|
||||
message: '请刷新页面重试',
|
||||
type: 'error',
|
||||
duration: 10000
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user