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._tasks: Dict[str, TaskRecord] = {}
|
||||||
self._lock = threading.Lock()
|
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 ----
|
# ---- public APIs ----
|
||||||
def create_chat_task(
|
def create_chat_task(
|
||||||
self,
|
self,
|
||||||
@ -274,6 +290,27 @@ task_manager = TaskManager()
|
|||||||
tasks_bp = Blueprint("tasks", __name__)
|
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"])
|
@tasks_bp.route("/api/tasks", methods=["GET"])
|
||||||
@api_login_required
|
@api_login_required
|
||||||
def list_tasks_api():
|
def list_tasks_api():
|
||||||
|
|||||||
@ -609,12 +609,33 @@ export const taskPollingMethods = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleTaskError(data: any) {
|
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);
|
console.error('[TaskPolling] 任务错误:', data.message);
|
||||||
this.uiPushToast({
|
this.uiPushToast({
|
||||||
title: '任务执行失败',
|
title,
|
||||||
message: data.message || '未知错误',
|
message,
|
||||||
type: 'error'
|
type: 'error',
|
||||||
|
duration: 8000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 清理状态
|
||||||
this.streamingMessage = false;
|
this.streamingMessage = false;
|
||||||
this.taskInProgress = false;
|
this.taskInProgress = false;
|
||||||
this.stopRequested = false;
|
this.stopRequested = false;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const useTaskStore = defineStore('task', {
|
|||||||
taskStatus: 'idle' as 'idle' | 'running' | 'succeeded' | 'failed' | 'canceled',
|
taskStatus: 'idle' as 'idle' | 'running' | 'succeeded' | 'failed' | 'canceled',
|
||||||
isPolling: false,
|
isPolling: false,
|
||||||
pollingError: null as string | null,
|
pollingError: null as string | null,
|
||||||
|
pollingErrorCount: 0, // 新增错误计数
|
||||||
taskCreatedAt: null as number | null,
|
taskCreatedAt: null as number | null,
|
||||||
taskUpdatedAt: null as number | null,
|
taskUpdatedAt: null as number | null,
|
||||||
}),
|
}),
|
||||||
@ -74,11 +75,12 @@ export const useTaskStore = defineStore('task', {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
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) {
|
if (!response.ok) {
|
||||||
throw new Error('轮询任务失败');
|
throw new Error(`HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@ -113,14 +115,27 @@ export const useTaskStore = defineStore('task', {
|
|||||||
this.stopPolling();
|
this.stopPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置错误计数
|
||||||
this.pollingError = null;
|
this.pollingError = null;
|
||||||
|
this.pollingErrorCount = 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Task] 轮询失败:', error);
|
this.pollingErrorCount++;
|
||||||
this.pollingError = error.message;
|
this.pollingError = error.message;
|
||||||
|
|
||||||
// 连续失败3次后停止轮询
|
console.error('[Task] 轮询失败:', error);
|
||||||
if (this.pollingError) {
|
|
||||||
// 可以在这里添加重试逻辑
|
// 连续失败 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