740 lines
28 KiB
Python
740 lines
28 KiB
Python
"""
|
||
统一调度器模块
|
||
提供统一的任务调度接口,支持时间触发、事件触发和自定义条件触发
|
||
"""
|
||
|
||
import asyncio
|
||
import uuid
|
||
from collections.abc import Awaitable, Callable
|
||
from datetime import datetime
|
||
from enum import Enum
|
||
from typing import Any
|
||
|
||
from src.common.logger import get_logger
|
||
from src.plugin_system.base.component_types import EventType
|
||
|
||
logger = get_logger("unified_scheduler")
|
||
|
||
|
||
class TriggerType(Enum):
|
||
"""触发类型枚举"""
|
||
|
||
TIME = "time" # 时间触发
|
||
EVENT = "event" # 事件触发(通过 event_manager)
|
||
CUSTOM = "custom" # 自定义条件触发
|
||
|
||
|
||
class ScheduleTask:
|
||
"""调度任务模型"""
|
||
|
||
def __init__(
|
||
self,
|
||
schedule_id: str,
|
||
callback: Callable[..., Awaitable[Any]],
|
||
trigger_type: TriggerType,
|
||
trigger_config: dict[str, Any],
|
||
is_recurring: bool = False,
|
||
task_name: str | None = None,
|
||
callback_args: tuple | None = None,
|
||
callback_kwargs: dict | None = None,
|
||
):
|
||
self.schedule_id = schedule_id
|
||
self.callback = callback
|
||
self.trigger_type = trigger_type
|
||
self.trigger_config = trigger_config
|
||
self.is_recurring = is_recurring
|
||
self.task_name = task_name or f"Task-{schedule_id[:8]}"
|
||
self.callback_args = callback_args or ()
|
||
self.callback_kwargs = callback_kwargs or {}
|
||
self.created_at = datetime.now()
|
||
self.last_triggered_at: datetime | None = None
|
||
self.trigger_count = 0
|
||
self.is_active = True
|
||
|
||
def __repr__(self) -> str:
|
||
return (
|
||
f"ScheduleTask(id={self.schedule_id[:8]}..., "
|
||
f"name={self.task_name}, type={self.trigger_type.value}, "
|
||
f"recurring={self.is_recurring}, active={self.is_active})"
|
||
)
|
||
|
||
|
||
class UnifiedScheduler:
|
||
"""统一调度器
|
||
|
||
提供统一的调度接口,支持:
|
||
1. 时间触发:指定时间点或延迟时间后触发
|
||
2. 事件触发:订阅 event_manager 的事件,当事件发生时触发
|
||
3. 自定义触发:通过自定义判断函数决定是否触发
|
||
|
||
特点:
|
||
- 每秒检查一次所有任务
|
||
- 自动执行到期任务
|
||
- 支持循环和一次性任务
|
||
- 提供任务管理API(创建、删除、强制触发等)
|
||
- 与 event_manager 集成,统一事件管理
|
||
"""
|
||
|
||
def __init__(self):
|
||
self._tasks: dict[str, ScheduleTask] = {}
|
||
self._running = False
|
||
self._check_task: asyncio.Task | None = None
|
||
self._lock = asyncio.Lock()
|
||
self._event_subscriptions: set[str] = set() # 追踪已订阅的事件
|
||
self._executing_tasks: dict[str, asyncio.Task] = {} # 追踪正在执行的任务
|
||
self._execution_lock = asyncio.Lock() # 专门用于保护执行任务的并发访问
|
||
|
||
async def _handle_event_trigger(self, event_name: str | EventType, event_params: dict[str, Any]) -> None:
|
||
"""处理来自 event_manager 的事件通知
|
||
|
||
此方法由 event_manager 在触发事件时直接调用
|
||
|
||
注意:此方法不能在持有 self._lock 的情况下调用,
|
||
否则会导致死锁(因为回调可能再次触发事件)
|
||
"""
|
||
# 获取订阅该事件的所有任务(快速复制,减少锁持有时间)
|
||
async with self._lock:
|
||
event_tasks = []
|
||
for task in self._tasks.values():
|
||
if (task.trigger_type == TriggerType.EVENT
|
||
and task.trigger_config.get("event_name") == event_name
|
||
and task.is_active):
|
||
|
||
# 检查事件任务是否已经在执行中,防止重复触发
|
||
if task.schedule_id in self._executing_tasks:
|
||
executing_task = self._executing_tasks[task.schedule_id]
|
||
if not executing_task.done():
|
||
logger.debug(f"[调度器] 事件任务 {task.task_name} 仍在执行中,跳过本次触发")
|
||
continue
|
||
else:
|
||
# 任务已完成但未清理,先清理
|
||
self._executing_tasks.pop(task.schedule_id, None)
|
||
|
||
event_tasks.append(task)
|
||
|
||
if not event_tasks:
|
||
logger.debug(f"[调度器] 事件 '{event_name}' 没有对应的调度任务")
|
||
return
|
||
|
||
logger.debug(f"[调度器] 事件 '{event_name}' 触发,共有 {len(event_tasks)} 个调度任务")
|
||
|
||
# 并发执行所有事件任务
|
||
async with self._execution_lock:
|
||
execution_tasks = []
|
||
for task in event_tasks:
|
||
execution_task = asyncio.create_task(
|
||
self._execute_event_task_callback(task, event_params),
|
||
name=f"execute_event_{task.task_name}"
|
||
)
|
||
execution_tasks.append(execution_task)
|
||
|
||
# 追踪正在执行的任务
|
||
self._executing_tasks[task.schedule_id] = execution_task
|
||
|
||
# 等待所有任务完成
|
||
results = await asyncio.gather(*execution_tasks, return_exceptions=True)
|
||
|
||
# 清理执行追踪
|
||
async with self._execution_lock:
|
||
for task in event_tasks:
|
||
self._executing_tasks.pop(task.schedule_id, None)
|
||
|
||
# 收集需要移除的任务
|
||
tasks_to_remove = []
|
||
for task, result in zip(event_tasks, results):
|
||
if isinstance(result, Exception):
|
||
logger.error(f"[调度器] 执行事件任务 {task.task_name} 时发生错误: {result}", exc_info=result)
|
||
elif result is True and not task.is_recurring:
|
||
# 成功执行且是一次性任务,标记为删除
|
||
tasks_to_remove.append(task.schedule_id)
|
||
logger.debug(f"[调度器] 一次性事件任务 {task.task_name} 已完成,将被移除")
|
||
|
||
# 移除已完成的一次性任务
|
||
if tasks_to_remove:
|
||
async with self._lock:
|
||
for schedule_id in tasks_to_remove:
|
||
await self._remove_task_internal(schedule_id)
|
||
|
||
async def start(self):
|
||
"""启动调度器"""
|
||
if self._running:
|
||
logger.warning("调度器已在运行中")
|
||
return
|
||
|
||
self._running = True
|
||
self._check_task = asyncio.create_task(self._check_loop())
|
||
|
||
# 注册回调到 event_manager
|
||
try:
|
||
from src.plugin_system.core.event_manager import event_manager
|
||
|
||
event_manager.register_scheduler_callback(self._handle_event_trigger)
|
||
logger.debug("调度器已注册到 event_manager")
|
||
except ImportError:
|
||
logger.warning("无法导入 event_manager,事件触发功能将不可用")
|
||
|
||
logger.info("统一调度器已启动")
|
||
|
||
async def stop(self):
|
||
"""停止调度器"""
|
||
if not self._running:
|
||
return
|
||
|
||
self._running = False
|
||
if self._check_task:
|
||
self._check_task.cancel()
|
||
try:
|
||
await self._check_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
|
||
# 取消注册回调
|
||
try:
|
||
from src.plugin_system.core.event_manager import event_manager
|
||
|
||
event_manager.unregister_scheduler_callback()
|
||
logger.debug("调度器回调已从 event_manager 注销")
|
||
except ImportError:
|
||
pass
|
||
|
||
# 取消所有正在执行的任务(避免在锁内进行阻塞操作)
|
||
executing_tasks = list(self._executing_tasks.values())
|
||
if executing_tasks:
|
||
logger.debug(f"取消 {len(executing_tasks)} 个正在执行的任务")
|
||
# 在取消任务前先清空追踪,避免死锁
|
||
self._executing_tasks.clear()
|
||
|
||
# 在锁外取消任务
|
||
for task in executing_tasks:
|
||
if not task.done():
|
||
task.cancel()
|
||
|
||
# 等待所有任务取消完成,使用较长的超时时间
|
||
try:
|
||
await asyncio.wait_for(
|
||
asyncio.gather(*executing_tasks, return_exceptions=True),
|
||
timeout=10.0
|
||
)
|
||
except asyncio.TimeoutError:
|
||
logger.warning("部分任务取消超时,强制停止")
|
||
|
||
logger.info("统一调度器已停止")
|
||
# 清空资源时不需要锁,因为已经停止运行
|
||
self._tasks.clear()
|
||
self._event_subscriptions.clear()
|
||
self._executing_tasks.clear()
|
||
|
||
async def _check_loop(self):
|
||
"""主循环:每秒检查一次所有任务"""
|
||
logger.debug("调度器检查循环已启动")
|
||
while self._running:
|
||
try:
|
||
await asyncio.sleep(1)
|
||
asyncio.create_task(self._check_and_trigger_tasks())
|
||
except asyncio.CancelledError:
|
||
logger.debug("调度器检查循环被取消")
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"调度器检查循环发生错误: {e}", exc_info=True)
|
||
|
||
async def _check_and_trigger_tasks(self):
|
||
"""检查并触发到期任务
|
||
|
||
注意:为了避免死锁和阻塞,回调执行必须在锁外并且并发进行
|
||
"""
|
||
current_time = datetime.now()
|
||
|
||
# 第一阶段:在锁内快速收集需要触发的任务
|
||
async with self._lock:
|
||
tasks_to_trigger = []
|
||
|
||
for schedule_id, task in list(self._tasks.items()):
|
||
if not task.is_active:
|
||
continue
|
||
|
||
# 检查任务是否已经在执行中,防止重复触发
|
||
if schedule_id in self._executing_tasks:
|
||
executing_task = self._executing_tasks[schedule_id]
|
||
if not executing_task.done():
|
||
logger.debug(f"[调度器] 任务 {task.task_name} 仍在执行中,跳过本次触发")
|
||
continue
|
||
else:
|
||
# 任务已完成但未清理,先清理
|
||
self._executing_tasks.pop(schedule_id, None)
|
||
|
||
try:
|
||
should_trigger = await self._should_trigger_task(task, current_time)
|
||
if should_trigger:
|
||
tasks_to_trigger.append(task)
|
||
except Exception as e:
|
||
logger.error(f"检查任务 {task.task_name} 时发生错误: {e}", exc_info=True)
|
||
|
||
# 第二阶段:在锁外并发执行所有回调(避免死锁和阻塞)
|
||
if not tasks_to_trigger:
|
||
return
|
||
|
||
# 为每个任务创建独立的异步任务,确保并发执行
|
||
async with self._execution_lock:
|
||
execution_tasks = []
|
||
for task in tasks_to_trigger:
|
||
execution_task = asyncio.create_task(
|
||
self._execute_task_callback(task, current_time),
|
||
name=f"execute_{task.task_name}"
|
||
)
|
||
execution_tasks.append(execution_task)
|
||
|
||
# 追踪正在执行的任务,以便在 remove_schedule 时可以取消
|
||
self._executing_tasks[task.schedule_id] = execution_task
|
||
|
||
# 等待所有任务完成(使用 return_exceptions=True 避免单个任务失败影响其他任务)
|
||
results = await asyncio.gather(*execution_tasks, return_exceptions=True)
|
||
|
||
# 清理执行追踪
|
||
async with self._execution_lock:
|
||
for task in tasks_to_trigger:
|
||
self._executing_tasks.pop(task.schedule_id, None)
|
||
|
||
# 第三阶段:收集需要移除的任务并在锁内移除
|
||
tasks_to_remove = []
|
||
for task, result in zip(tasks_to_trigger, results):
|
||
if isinstance(result, Exception):
|
||
logger.error(f"[调度器] 执行任务 {task.task_name} 时发生错误: {result}", exc_info=result)
|
||
elif result is True and not task.is_recurring:
|
||
# 成功执行且是一次性任务,标记为删除
|
||
tasks_to_remove.append(task.schedule_id)
|
||
logger.debug(f"[调度器] 一次性任务 {task.task_name} 已完成,将被移除")
|
||
|
||
if tasks_to_remove:
|
||
async with self._lock:
|
||
for schedule_id in tasks_to_remove:
|
||
await self._remove_task_internal(schedule_id)
|
||
|
||
async def _execute_task_callback(self, task: ScheduleTask, current_time: datetime) -> bool:
|
||
"""执行单个任务的回调(用于并发执行)
|
||
|
||
Args:
|
||
task: 要执行的任务
|
||
current_time: 当前时间
|
||
|
||
Returns:
|
||
bool: 执行是否成功
|
||
"""
|
||
try:
|
||
logger.debug(f"[调度器] 触发任务: {task.task_name}")
|
||
|
||
# 执行回调
|
||
await self._execute_callback(task)
|
||
|
||
# 更新任务状态
|
||
task.last_triggered_at = current_time
|
||
task.trigger_count += 1
|
||
|
||
logger.debug(f"[调度器] 任务 {task.task_name} 执行完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"[调度器] 执行任务 {task.task_name} 时发生错误: {e}", exc_info=True)
|
||
return False
|
||
|
||
async def _execute_event_task_callback(self, task: ScheduleTask, event_params: dict[str, Any]) -> bool:
|
||
"""执行单个事件任务的回调(用于并发执行)
|
||
|
||
Args:
|
||
task: 要执行的任务
|
||
event_params: 事件参数
|
||
|
||
Returns:
|
||
bool: 执行是否成功
|
||
"""
|
||
try:
|
||
logger.debug(f"[调度器] 执行事件任务: {task.task_name}")
|
||
|
||
current_time = datetime.now()
|
||
|
||
# 执行回调,传入事件参数
|
||
if event_params:
|
||
if asyncio.iscoroutinefunction(task.callback):
|
||
await task.callback(**event_params)
|
||
else:
|
||
task.callback(**event_params)
|
||
else:
|
||
await self._execute_callback(task)
|
||
|
||
# 更新任务状态
|
||
task.last_triggered_at = current_time
|
||
task.trigger_count += 1
|
||
|
||
logger.debug(f"[调度器] 事件任务 {task.task_name} 执行完成")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"[调度器] 执行事件任务 {task.task_name} 时发生错误: {e}", exc_info=True)
|
||
return False
|
||
|
||
async def _execute_trigger_task_callback(self, task: ScheduleTask) -> bool:
|
||
"""执行强制触发的任务回调
|
||
|
||
Args:
|
||
task: 要执行的任务
|
||
|
||
Returns:
|
||
bool: 执行是否成功
|
||
"""
|
||
try:
|
||
logger.debug(f"[调度器] 强制触发任务: {task.task_name}")
|
||
|
||
# 执行回调
|
||
await self._execute_callback(task)
|
||
|
||
# 更新任务状态
|
||
current_time = datetime.now()
|
||
task.last_triggered_at = current_time
|
||
task.trigger_count += 1
|
||
|
||
logger.debug(f"[调度器] 强制触发任务 {task.task_name} 执行完成")
|
||
|
||
# 如果不是循环任务,需要移除
|
||
if not task.is_recurring:
|
||
await self._remove_task_internal(task.schedule_id)
|
||
logger.debug(f"[调度器] 一次性任务 {task.task_name} 已完成并移除")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"[调度器] 强制触发任务 {task.task_name} 时发生错误: {e}", exc_info=True)
|
||
return False
|
||
|
||
async def _should_trigger_task(self, task: ScheduleTask, current_time: datetime) -> bool:
|
||
"""判断任务是否应该触发"""
|
||
if task.trigger_type == TriggerType.TIME:
|
||
return await self._check_time_trigger(task, current_time)
|
||
elif task.trigger_type == TriggerType.CUSTOM:
|
||
return await self._check_custom_trigger(task)
|
||
# EVENT 类型由 event_manager 触发,不在这里处理
|
||
return False
|
||
|
||
async def _check_time_trigger(self, task: ScheduleTask, current_time: datetime) -> bool:
|
||
"""检查时间触发条件"""
|
||
config = task.trigger_config
|
||
|
||
if "trigger_at" in config:
|
||
trigger_time = config["trigger_at"]
|
||
if isinstance(trigger_time, str):
|
||
trigger_time = datetime.fromisoformat(trigger_time)
|
||
|
||
if task.is_recurring and "interval_seconds" in config:
|
||
if task.last_triggered_at is None:
|
||
return current_time >= trigger_time
|
||
else:
|
||
elapsed = (current_time - task.last_triggered_at).total_seconds()
|
||
return elapsed >= config["interval_seconds"]
|
||
else:
|
||
return current_time >= trigger_time
|
||
|
||
elif "delay_seconds" in config:
|
||
if task.last_triggered_at is None:
|
||
elapsed = (current_time - task.created_at).total_seconds()
|
||
return elapsed >= config["delay_seconds"]
|
||
else:
|
||
elapsed = (current_time - task.last_triggered_at).total_seconds()
|
||
return elapsed >= config["delay_seconds"]
|
||
|
||
return False
|
||
|
||
async def _check_custom_trigger(self, task: ScheduleTask) -> bool:
|
||
"""检查自定义触发条件"""
|
||
condition_func = task.trigger_config.get("condition_func")
|
||
if not condition_func or not callable(condition_func):
|
||
logger.warning(f"任务 {task.task_name} 的自定义条件函数无效")
|
||
return False
|
||
|
||
try:
|
||
if asyncio.iscoroutinefunction(condition_func):
|
||
result = await condition_func()
|
||
else:
|
||
result = condition_func()
|
||
return bool(result)
|
||
except Exception as e:
|
||
logger.error(f"执行任务 {task.task_name} 的自定义条件函数时出错: {e}", exc_info=True)
|
||
return False
|
||
|
||
async def _execute_callback(self, task: ScheduleTask):
|
||
"""执行任务回调函数"""
|
||
try:
|
||
logger.debug(f"触发任务: {task.task_name}")
|
||
|
||
if asyncio.iscoroutinefunction(task.callback):
|
||
await task.callback(*task.callback_args, **task.callback_kwargs)
|
||
else:
|
||
task.callback(*task.callback_args, **task.callback_kwargs)
|
||
|
||
logger.debug(f"任务 {task.task_name} 执行完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行任务 {task.task_name} 的回调函数时出错: {e}", exc_info=True)
|
||
|
||
async def _remove_task_internal(self, schedule_id: str):
|
||
"""内部方法:移除任务(需要加锁保护)"""
|
||
async with self._lock:
|
||
task = self._tasks.pop(schedule_id, None)
|
||
if task:
|
||
if task.trigger_type == TriggerType.EVENT:
|
||
event_name = task.trigger_config.get("event_name")
|
||
if event_name:
|
||
has_other_subscribers = any(
|
||
t.trigger_type == TriggerType.EVENT and t.trigger_config.get("event_name") == event_name
|
||
for t in self._tasks.values()
|
||
)
|
||
# 如果没有其他任务订阅此事件,从追踪集合中移除
|
||
if not has_other_subscribers and event_name in self._event_subscriptions:
|
||
self._event_subscriptions.discard(event_name)
|
||
logger.debug(f"事件 '{event_name}' 已无订阅任务,从追踪中移除")
|
||
|
||
async def create_schedule(
|
||
self,
|
||
callback: Callable[..., Awaitable[Any]],
|
||
trigger_type: TriggerType,
|
||
trigger_config: dict[str, Any],
|
||
is_recurring: bool = False,
|
||
task_name: str | None = None,
|
||
callback_args: tuple | None = None,
|
||
callback_kwargs: dict | None = None,
|
||
) -> str:
|
||
"""创建调度任务(详细注释见文档)"""
|
||
schedule_id = str(uuid.uuid4())
|
||
|
||
task = ScheduleTask(
|
||
schedule_id=schedule_id,
|
||
callback=callback,
|
||
trigger_type=trigger_type,
|
||
trigger_config=trigger_config,
|
||
is_recurring=is_recurring,
|
||
task_name=task_name,
|
||
callback_args=callback_args,
|
||
callback_kwargs=callback_kwargs,
|
||
)
|
||
|
||
async with self._lock:
|
||
self._tasks[schedule_id] = task
|
||
|
||
if trigger_type == TriggerType.EVENT:
|
||
event_name = trigger_config.get("event_name")
|
||
if not event_name:
|
||
raise ValueError("事件触发类型必须提供 event_name")
|
||
|
||
# 添加到追踪集合
|
||
if event_name not in self._event_subscriptions:
|
||
self._event_subscriptions.add(event_name)
|
||
logger.debug(f"开始追踪事件: {event_name}")
|
||
|
||
logger.debug(f"创建调度任务: {task.task_name}")
|
||
return schedule_id
|
||
|
||
async def remove_schedule(self, schedule_id: str) -> bool:
|
||
"""移除调度任务
|
||
|
||
如果任务正在执行,会取消执行中的任务
|
||
"""
|
||
# 先获取任务信息和执行任务,避免长时间持有锁
|
||
async with self._lock:
|
||
if schedule_id not in self._tasks:
|
||
logger.warning(f"尝试移除不存在的任务: {schedule_id}")
|
||
return False
|
||
|
||
task = self._tasks[schedule_id]
|
||
executing_task = self._executing_tasks.get(schedule_id)
|
||
|
||
# 在锁外取消正在执行的任务,避免死锁
|
||
if executing_task and not executing_task.done():
|
||
logger.debug(f"取消正在执行的任务: {task.task_name}")
|
||
try:
|
||
executing_task.cancel()
|
||
await asyncio.wait_for(executing_task, 3)
|
||
except asyncio.TimeoutError:
|
||
logger.warning(f"取消任务 {task.task_name} 超时,强制移除")
|
||
except Exception as e:
|
||
logger.warning(f"取消任务 {task.task_name} 时发生错误: {e}")
|
||
|
||
# 重新获取锁移除任务
|
||
async with self._lock:
|
||
await self._remove_task_internal(schedule_id)
|
||
|
||
# 使用执行锁清理执行追踪
|
||
async with self._execution_lock:
|
||
self._executing_tasks.pop(schedule_id, None)
|
||
|
||
logger.debug(f"移除调度任务: {task.task_name}")
|
||
return True
|
||
|
||
async def trigger_schedule(self, schedule_id: str) -> bool:
|
||
"""强制触发指定任务"""
|
||
# 先获取任务信息,减少锁持有时间
|
||
async with self._lock:
|
||
task = self._tasks.get(schedule_id)
|
||
if not task:
|
||
logger.warning(f"尝试触发不存在的任务: {schedule_id}")
|
||
return False
|
||
|
||
if not task.is_active:
|
||
logger.warning(f"尝试触发已停用的任务: {task.task_name}")
|
||
return False
|
||
|
||
# 检查任务是否已经在执行中
|
||
executing_task = self._executing_tasks.get(schedule_id)
|
||
if executing_task and not executing_task.done():
|
||
logger.warning(f"任务 {task.task_name} 已在执行中,无法重复触发")
|
||
return False
|
||
|
||
# 清理已完成的任务
|
||
if executing_task and executing_task.done():
|
||
self._executing_tasks.pop(schedule_id, None)
|
||
|
||
# 在锁外创建执行任务
|
||
async with self._execution_lock:
|
||
execution_task = asyncio.create_task(
|
||
self._execute_trigger_task_callback(task),
|
||
name=f"trigger_{task.task_name}"
|
||
)
|
||
|
||
# 追踪执行任务
|
||
self._executing_tasks[schedule_id] = execution_task
|
||
|
||
# 在锁外等待任务完成
|
||
try:
|
||
result = await execution_task
|
||
return result
|
||
finally:
|
||
# 清理执行追踪
|
||
async with self._execution_lock:
|
||
self._executing_tasks.pop(schedule_id, None)
|
||
|
||
async def pause_schedule(self, schedule_id: str) -> bool:
|
||
"""暂停任务(不删除)"""
|
||
async with self._lock:
|
||
task = self._tasks.get(schedule_id)
|
||
if not task:
|
||
logger.warning(f"尝试暂停不存在的任务: {schedule_id}")
|
||
return False
|
||
|
||
task.is_active = False
|
||
logger.debug(f"暂停任务: {task.task_name}")
|
||
return True
|
||
|
||
async def resume_schedule(self, schedule_id: str) -> bool:
|
||
"""恢复任务"""
|
||
async with self._lock:
|
||
task = self._tasks.get(schedule_id)
|
||
if not task:
|
||
logger.warning(f"尝试恢复不存在的任务: {schedule_id}")
|
||
return False
|
||
|
||
task.is_active = True
|
||
logger.debug(f"恢复任务: {task.task_name}")
|
||
return True
|
||
|
||
async def get_task_info(self, schedule_id: str) -> dict[str, Any] | None:
|
||
"""获取任务信息"""
|
||
async with self._lock:
|
||
task = self._tasks.get(schedule_id)
|
||
if not task:
|
||
return None
|
||
|
||
return {
|
||
"schedule_id": task.schedule_id,
|
||
"task_name": task.task_name,
|
||
"trigger_type": task.trigger_type.value,
|
||
"is_recurring": task.is_recurring,
|
||
"is_active": task.is_active,
|
||
"created_at": task.created_at.isoformat(),
|
||
"last_triggered_at": task.last_triggered_at.isoformat() if task.last_triggered_at else None,
|
||
"trigger_count": task.trigger_count,
|
||
"trigger_config": task.trigger_config.copy(),
|
||
}
|
||
|
||
async def list_tasks(self, trigger_type: TriggerType | None = None) -> list[dict[str, Any]]:
|
||
"""列出所有任务或指定类型的任务"""
|
||
async with self._lock:
|
||
tasks = []
|
||
for task in self._tasks.values():
|
||
if trigger_type is None or task.trigger_type == trigger_type:
|
||
task_info = await self.get_task_info(task.schedule_id)
|
||
if task_info:
|
||
tasks.append(task_info)
|
||
return tasks
|
||
|
||
def get_statistics(self) -> dict[str, Any]:
|
||
"""获取调度器统计信息"""
|
||
total_tasks = len(self._tasks)
|
||
active_tasks = sum(1 for task in self._tasks.values() if task.is_active)
|
||
recurring_tasks = sum(1 for task in self._tasks.values() if task.is_recurring)
|
||
executing_tasks = sum(1 for task in self._executing_tasks.values() if not task.done())
|
||
|
||
tasks_by_type = {
|
||
TriggerType.TIME.value: 0,
|
||
TriggerType.EVENT.value: 0,
|
||
TriggerType.CUSTOM.value: 0,
|
||
}
|
||
|
||
for task in self._tasks.values():
|
||
tasks_by_type[task.trigger_type.value] += 1
|
||
|
||
return {
|
||
"is_running": self._running,
|
||
"total_tasks": total_tasks,
|
||
"active_tasks": active_tasks,
|
||
"paused_tasks": total_tasks - active_tasks,
|
||
"recurring_tasks": recurring_tasks,
|
||
"one_time_tasks": total_tasks - recurring_tasks,
|
||
"executing_tasks": executing_tasks,
|
||
"tasks_by_type": tasks_by_type,
|
||
"registered_events": list(self._event_subscriptions),
|
||
}
|
||
|
||
|
||
# 全局调度器实例
|
||
unified_scheduler = UnifiedScheduler()
|
||
|
||
async def initialize_scheduler():
|
||
"""初始化调度器
|
||
|
||
这个函数应该在 bot 启动时调用
|
||
"""
|
||
try:
|
||
logger.info("正在启动统一调度器...")
|
||
await unified_scheduler.start()
|
||
logger.info("统一调度器启动成功")
|
||
|
||
# 获取初始统计信息
|
||
stats = unified_scheduler.get_statistics()
|
||
logger.info(f"调度器状态: {stats}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"启动统一调度器失败: {e}", exc_info=True)
|
||
raise
|
||
|
||
|
||
async def shutdown_scheduler():
|
||
"""关闭调度器
|
||
|
||
这个函数应该在 bot 关闭时调用
|
||
"""
|
||
try:
|
||
logger.info("正在关闭统一调度器...")
|
||
|
||
# 显示最终统计
|
||
stats = unified_scheduler.get_statistics()
|
||
logger.info(f"调度器最终统计: {stats}")
|
||
|
||
# 列出剩余任务
|
||
remaining_tasks = await unified_scheduler.list_tasks()
|
||
if remaining_tasks:
|
||
logger.warning(f"检测到 {len(remaining_tasks)} 个未清理的任务:")
|
||
for task in remaining_tasks:
|
||
logger.warning(f" - {task['task_name']} (ID: {task['schedule_id'][:8]}...)")
|
||
|
||
await unified_scheduler.stop()
|
||
logger.info("统一调度器已关闭")
|
||
|
||
except Exception as e:
|
||
logger.error(f"关闭统一调度器失败: {e}", exc_info=True)
|