mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-21 14:17:11 +08:00
feat(scheduler): run existing tasks immediately
This commit is contained in:
@@ -41,6 +41,8 @@ class SchedulerService:
|
||||
self.running = False
|
||||
self.thread = None
|
||||
self._lock = threading.Lock()
|
||||
self._execution_lock = threading.Lock()
|
||||
self._active_task_ids = set()
|
||||
|
||||
def start(self):
|
||||
"""Start the scheduler service"""
|
||||
@@ -85,7 +87,15 @@ class SchedulerService:
|
||||
try:
|
||||
if self._is_task_due(task, now):
|
||||
logger.info(f"[Scheduler] Executing task: {task['id']} - {task['name']}")
|
||||
ok = self._execute_task(task)
|
||||
if not self._claim_task(task['id']):
|
||||
logger.info(
|
||||
f"[Scheduler] Task {task['id']} is already running; skipping this tick"
|
||||
)
|
||||
continue
|
||||
try:
|
||||
ok = self._execute_task(task)
|
||||
finally:
|
||||
self._release_task(task['id'])
|
||||
if not ok:
|
||||
# Leave next_run_at as-is so the next loop retries.
|
||||
# Cron tasks within the catch-up window will keep
|
||||
@@ -106,6 +116,57 @@ class SchedulerService:
|
||||
logger.info(f"[Scheduler] One-time task completed and removed: {task['id']}")
|
||||
except Exception as e:
|
||||
logger.error(f"[Scheduler] Error processing task {task.get('id')}: {e}")
|
||||
|
||||
def run_task_now(self, task_id: str) -> None:
|
||||
"""Queue one immediate execution without changing the task schedule.
|
||||
|
||||
Disabled and one-time tasks may be run manually for testing. The
|
||||
stored ``next_run_at`` remains unchanged, so a manual run never
|
||||
consumes or delays the next scheduled occurrence.
|
||||
|
||||
Raises:
|
||||
ValueError: if the task does not exist.
|
||||
RuntimeError: if the same task is already executing.
|
||||
"""
|
||||
task = self.task_store.get_task(task_id)
|
||||
if not task:
|
||||
raise ValueError(f"Task '{task_id}' not found")
|
||||
if not self._claim_task(task_id):
|
||||
raise RuntimeError(f"Task '{task_id}' is already running")
|
||||
|
||||
def _run():
|
||||
now = datetime.now()
|
||||
try:
|
||||
logger.info(f"[Scheduler] Manually executing task: {task_id} - {task.get('name', '')}")
|
||||
ok = self._execute_task(task)
|
||||
if ok:
|
||||
self.task_store.update_task(task_id, {
|
||||
"last_run_at": now.isoformat(),
|
||||
"last_manual_run_at": now.isoformat(),
|
||||
})
|
||||
logger.info(f"[Scheduler] Manual execution completed: {task_id}")
|
||||
else:
|
||||
logger.warning(f"[Scheduler] Manual execution failed: {task_id}")
|
||||
finally:
|
||||
self._release_task(task_id)
|
||||
|
||||
threading.Thread(
|
||||
target=_run,
|
||||
daemon=True,
|
||||
name=f"scheduler-manual-{task_id}",
|
||||
).start()
|
||||
|
||||
def _claim_task(self, task_id: str) -> bool:
|
||||
"""Prevent scheduled and manual runs of the same task from overlapping."""
|
||||
with self._execution_lock:
|
||||
if task_id in self._active_task_ids:
|
||||
return False
|
||||
self._active_task_ids.add(task_id)
|
||||
return True
|
||||
|
||||
def _release_task(self, task_id: str) -> None:
|
||||
with self._execution_lock:
|
||||
self._active_task_ids.discard(task_id)
|
||||
|
||||
def _is_task_due(self, task: dict, now: datetime) -> bool:
|
||||
"""
|
||||
|
||||
@@ -24,6 +24,7 @@ class SchedulerTool(BaseTool):
|
||||
"⚠️ 重要:仅当需要「定时/提醒/每天/每周/X分钟后/X点」等延迟或周期执行时才使用此工具。"
|
||||
"使用方法:\n"
|
||||
"- 创建:action='create', name='任务名', message/ai_task='内容', schedule_type='once/interval/cron', schedule_value='...'\n"
|
||||
"- 立即执行:action='run', task_id='任务ID'(不改变原调度时间)\n"
|
||||
"- 查询:action='list' / action='get', task_id='任务ID'\n"
|
||||
"- 管理:action='delete/enable/disable', task_id='任务ID'\n\n"
|
||||
"调度类型:\n"
|
||||
@@ -37,12 +38,12 @@ class SchedulerTool(BaseTool):
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["create", "list", "get", "delete", "enable", "disable"],
|
||||
"description": "操作类型: create(创建), list(列表), get(查询), delete(删除), enable(启用), disable(禁用)"
|
||||
"enum": ["create", "run", "list", "get", "delete", "enable", "disable"],
|
||||
"description": "操作类型: create(创建), run(立即执行), list(列表), get(查询), delete(删除), enable(启用), disable(禁用)"
|
||||
},
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "任务ID (用于 get/delete/enable/disable 操作)"
|
||||
"description": "任务ID (用于 run/get/delete/enable/disable 操作)"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -88,7 +89,7 @@ class SchedulerTool(BaseTool):
|
||||
|
||||
Args:
|
||||
params: Dictionary containing:
|
||||
- action: Operation type (create/list/get/delete/enable/disable)
|
||||
- action: Operation type (create/run/list/get/delete/enable/disable)
|
||||
- Other parameters depending on action
|
||||
|
||||
Returns:
|
||||
@@ -105,6 +106,9 @@ class SchedulerTool(BaseTool):
|
||||
if action == "create":
|
||||
result = self._create_task(**kwargs)
|
||||
return ToolResult.success(result)
|
||||
elif action == "run":
|
||||
result = self._run_task(**kwargs)
|
||||
return ToolResult.success(result)
|
||||
elif action == "list":
|
||||
result = self._list_tasks(**kwargs)
|
||||
return ToolResult.success(result)
|
||||
@@ -303,6 +307,24 @@ class SchedulerTool(BaseTool):
|
||||
|
||||
self.task_store.delete_task(task_id)
|
||||
return f"✅ 任务 '{task['name']}' ({task_id}) 已删除"
|
||||
|
||||
def _run_task(self, **kwargs) -> str:
|
||||
"""Queue an existing task for immediate execution."""
|
||||
task_id = kwargs.get("task_id")
|
||||
if not task_id:
|
||||
return "错误: 缺少任务ID (task_id)"
|
||||
|
||||
task = self.task_store.get_task(task_id)
|
||||
if not task:
|
||||
return f"错误: 任务 '{task_id}' 不存在"
|
||||
|
||||
from agent.tools.scheduler.integration import get_scheduler_service
|
||||
service = get_scheduler_service()
|
||||
if not service:
|
||||
return "错误: 定时任务系统未运行"
|
||||
|
||||
service.run_task_now(task_id)
|
||||
return f"✅ 任务 '{task['name']}' ({task_id}) 已开始立即执行,原调度时间保持不变"
|
||||
|
||||
def _enable_task(self, **kwargs) -> str:
|
||||
"""Enable a task"""
|
||||
|
||||
Reference in New Issue
Block a user