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:
JOJO 2026-04-04 01:04:35 +08:00
parent de82a37958
commit 8d6c59deed
3 changed files with 82 additions and 9 deletions

View File

@ -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():

View File

@ -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;

View File

@ -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
});
}
}
}
},