fish_async_task.performance.priority_cleanup 源代码
"""
优先级队列清理模块
本模块提供 TaskStatusWithExpiry 类,使用优先级队列(最小堆)
跟踪任务过期时间,支持高效的增量清理操作。
核心思想:
- 使用 heapq 维护 (expiry_time, task_id) 的最小堆
- 清理操作只处理已过期的任务,复杂度 O(k log n)
- 增量清理避免长时间阻塞
"""
import heapq
import logging
import threading
import time
from typing import Any, Dict, Optional
from fish_async_task.performance._logging import get_logger
from fish_async_task.performance._utils import compute_expiry_time
from fish_async_task.types import TaskStatusDict
[文档]
class TaskStatusWithExpiry:
"""
带过期时间的任务状态存储
使用优先级队列(最小堆)跟踪任务过期时间,
支持高效的增量清理操作。
Attributes:
ttl: 任务状态生存时间(秒)
logger: 日志记录器
"""
[文档]
def __init__(self, ttl: int = 300) -> None:
"""
初始化带过期时间的任务状态存储
Args:
ttl: 任务状态生存时间(秒),默认 300(5 分钟)
Note:
清理操作会移除超过 TTL 的任务状态
"""
if ttl < 1:
raise ValueError(f"ttl 必须 >= 1,当前值: {ttl}")
self.ttl = ttl
self.logger = get_logger()
# 任务状态字典
self.status_dict: Dict[str, TaskStatusDict] = {}
# 优先级队列(最小堆):存储 (expiry_time, task_id)
self.expiry_heap: list[tuple[float, str]] = []
# 优先级队列的锁
self.heap_lock = threading.Lock()
self.logger.info(f"初始化优先级队列清理存储:TTL={ttl}秒")
[文档]
def add_task(self, task_id: str, status: TaskStatusDict) -> None:
"""
添加任务状态
Args:
task_id: 任务 ID
status: 任务状态字典,必须包含 end_time 字段
Behavior:
- 将任务添加到 status_dict
- 如果有 end_time,计算过期时间并添加到优先级队列
Raises:
ValueError: 如果 status 不包含 end_time
Examples:
>>> store = TaskStatusWithExpiry(ttl=300)
>>> store.add_task("task-123", {
... "task_id": "task-123",
... "status": "completed",
... "end_time": time.time()
... })
"""
with self.heap_lock:
# 添加到状态字典
self.status_dict[task_id] = status
# 如果有 end_time,添加到优先级队列
end_time = status.get("end_time")
if end_time is not None:
expiry_time = compute_expiry_time(end_time, self.ttl)
heapq.heappush(self.expiry_heap, (expiry_time, task_id))
self.logger.debug(
f"添加任务到优先级队列: task_id={task_id}, expiry_time={expiry_time}"
)
[文档]
def get_task(self, task_id: str) -> Optional[TaskStatusDict]:
"""
获取任务状态
Args:
task_id: 任务 ID
Returns:
任务状态字典,如果不存在返回 None
Note:
此方法不锁定优先级队列(只读操作)
"""
with self.heap_lock:
return self.status_dict.get(task_id)
[文档]
def cleanup_expired(self, max_cleanup: Optional[int] = None) -> int:
"""
清理过期任务(增量清理)
Args:
max_cleanup: 最大清理数量,None 表示清理所有过期任务
默认 None
Returns:
清理的任务数量
Performance:
O(k log n) 时间复杂度,k 为过期任务数量
通常 k << n,因此远快于全量扫描 O(n)
Thread-Safety:
线程安全,使用 heap_lock 保护
Note:
增量清理:每次最多清理 max_cleanup 个任务,
避免长时间阻塞其他操作
Examples:
>>> store = TaskStatusWithExpiry(ttl=300)
>>> # 添加过期任务...
>>> cleaned_count = store.cleanup_expired(max_cleanup=100)
>>> print(f"清理了 {cleaned_count} 个过期任务")
"""
with self.heap_lock:
cleaned_count = 0
current_time = time.time()
# 清理过期任务
while self.expiry_heap:
# 检查是否达到最大清理数量
if max_cleanup is not None and cleaned_count >= max_cleanup:
break
# 查看堆顶元素(最早过期的任务)
expiry_time, task_id = self.expiry_heap[0]
# 如果堆顶任务未过期,则后续任务都未过期
if expiry_time > current_time:
break
# 弹出堆顶元素
heapq.heappop(self.expiry_heap)
# 从状态字典中删除(如果存在)
if task_id in self.status_dict:
# 验证确实已过期(双重检查)
status = self.status_dict[task_id]
status_end_time: Optional[float] = status.get("end_time")
if status_end_time is not None and current_time - status_end_time > self.ttl:
del self.status_dict[task_id]
cleaned_count += 1
if cleaned_count > 0:
self.logger.info(f"清理过期任务: 数量={cleaned_count}, max_cleanup={max_cleanup}")
return cleaned_count
[文档]
def enforce_max_count(self, max_count: int) -> int:
"""
强制执行最大任务数量限制
当任务数量超过 max_count 时,删除最旧的任务(按 submit_time 或 start_time)。
Args:
max_count: 最大任务数量
Returns:
删除的任务数量
Performance:
O(n log n) 时间复杂度,n 为任务总数
Thread-Safety:
线程安全,使用 heap_lock 保护
Examples:
>>> store = TaskStatusWithExpiry(ttl=300)
>>> # 添加大量任务...
>>> removed_count = store.enforce_max_count(max_count=10000)
>>> print(f"移除了 {removed_count} 个旧任务")
"""
with self.heap_lock:
current_count = len(self.status_dict)
if current_count <= max_count:
return 0
# 需要删除的任务数量
to_remove = current_count - max_count
# 收集所有任务及其时间戳
tasks_with_time = []
for task_id, status in self.status_dict.items():
# 优先使用 submit_time,如果没有则使用 start_time,最后使用 end_time
timestamp = (
status.get("submit_time")
or status.get("start_time")
or status.get("end_time")
or 0.0
)
tasks_with_time.append((timestamp, task_id))
# 按时间戳排序(最旧的在前)
tasks_with_time.sort(key=lambda x: x[0])
# 收集要删除的 task_id
to_remove_ids = set(task_id for _, task_id in tasks_with_time[:to_remove])
# 从 status_dict 中删除
removed_count = 0
for task_id in to_remove_ids:
if task_id in self.status_dict:
del self.status_dict[task_id]
removed_count += 1
# 一次性重建堆,排除已删除的 task_id(O(n) 操作)
self.expiry_heap = [
(exp_time, tid) for exp_time, tid in self.expiry_heap if tid not in to_remove_ids
]
heapq.heapify(self.expiry_heap)
if removed_count > 0:
self.logger.info(
f"强制执行最大数量限制: 删除={removed_count}, max_count={max_count}"
)
return removed_count
[文档]
def get_task_count(self) -> int:
"""
获取当前任务数量
Returns:
任务状态字典中的任务数量
Performance:
O(1) 时间复杂度
Thread-Safety:
线程安全(返回近似值,但非常准确)
Examples:
>>> store = TaskStatusWithExpiry()
>>> store.add_task("task-123", {"end_time": time.time()})
>>> store.get_task_count()
1
"""
with self.heap_lock:
return len(self.status_dict)
[文档]
def get_all_statuses(self) -> Dict[str, TaskStatusDict]:
"""
获取所有任务状态
Returns:
所有任务状态的字典
Note:
此方法会锁定优先级队列,避免在清理期间调用
"""
with self.heap_lock:
return dict(self.status_dict) # 返回副本