Files
paste-framework/paste/core/aio_pool.py
T
2026-06-02 16:26:10 +08:00

154 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
import datetime
from typing import Optional, Callable, Set, Any
import psutil
from dateutil.relativedelta import relativedelta
from paste.core.logging import echo_log
MAX_BACKGROUND_TASKS = 3000
"""
最大任务数量,根据服务器内存调整。
"""
aio_loop: Optional[asyncio.AbstractEventLoop] = None
"""
异步循环对象。
"""
aio_runner: Optional[Callable] = None
"""
异步方法运行器,对应::
asyncio.events.AbstractEventLoop.run_until_complete()
方法的返回值 。
"""
global_background_tasks: Set[asyncio.Task] = set()
"""
全局后台任务池。
"""
async def run_background_task(coro, max_tasks: int = MAX_BACKGROUND_TASKS):
"""
运行后台任务。
:param coro: 要在后台执行的协程。
:param max_tasks: 背压总量
"""
global MAX_BACKGROUND_TASKS
if max_tasks != MAX_BACKGROUND_TASKS:
MAX_BACKGROUND_TASKS = max_tasks
if len(global_background_tasks) >= MAX_BACKGROUND_TASKS:
# 增加背压控制
await asyncio.wait(global_background_tasks, return_when=asyncio.FIRST_COMPLETED)
task = asyncio.create_task(coro)
global_background_tasks.add(task)
# 任务完成后自动移除
task.add_done_callback(global_background_tasks.discard)
def get_aio_loop():
"""
这里必须采用方法,在适当的时间点创建事件循环对象,否则会导致服务无法启动。
主要是测试到 EventLoop 之间的冲突,或异步事件已经在运行,导致无法顺利执行。
:return: 事件循环对象
"""
global aio_loop
if aio_loop:
return aio_loop
else:
try:
# 尝试获取当前运行中的事件循环
aio_loop = asyncio.get_running_loop()
except RuntimeError:
# 如果没有运行中的循环,才创建新的
aio_loop = asyncio.new_event_loop()
return aio_loop
def get_aio_runner():
"""
这里必须采用方法,在适当的时间点创建事件循环对象,否则会导致服务无法启动。
主要是测试到 EventLoop 之间的冲突,或异步事件已经在运行,导致无法顺利执行。
:return: 运行器对象
"""
global aio_loop, aio_runner
if aio_runner:
return aio_runner
else:
aio_runner = get_aio_loop().run_until_complete
return aio_runner
def process_info(pid: int):
"""
若传入的 PID 存在,则返回进程信息,否则返回 None。
:param pid: 进程 ID
:return: 进程信息
"""
try:
process = psutil.Process(pid)
_delta = relativedelta(datetime.datetime.now(), datetime.datetime.fromtimestamp(process.create_time()))
_d, _h, _m, _s = _delta.days, _delta.hours, _delta.minutes, _delta.seconds
process.cpu_percent()
return {
'name': process.name(),
'cpu_usage': f"{process.cpu_percent()}%",
'memory_usage': f"{process.memory_info().rss / (1024 * 1024):.3f}MB",
'running_time': f"{_d}{_h}{_m}{_s}",
}
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
return None
async def gather_with_retry(*coro_constructors: Callable[[], Any],
max_retries: int = 3,
eba: int = 0.5,
return_exceptions: bool = False) -> tuple[Any, ...]:
"""
封装 asyncio.gather,支持对一组并发任务进行 N 次重试。
与 asyncio.gather 对齐:
- 使用 *args 传参,而非列表
- 支持 return_exceptions 参数
- 返回 list 类型,顺序一致
但要求:每个参数必须是一个**无参函数**,调用后返回一个 awaitable。
这样才能在每次重试时重新创建任务。
Args:
*coro_constructors: Callable 对象,async 方法要用 lambda 封装
max_retries (int): 最大重试次数(总尝试次数 = max_retries + 1
eba (int): 指数退避的起始等待时间
return_exceptions (bool): 若为 True,异常作为结果返回,不抛出
Returns:
list: 所有任务结果列表。若 return_exceptions=True,异常也会作为列表元素。
Raises:
Exception: 当 return_exceptions=False 且所有重试都失败时,抛出最后一次尝试中的第一个异常。
"""
for attempt in range(max_retries + 1):
try:
tasks = [ctor() for ctor in coro_constructors]
results = await asyncio.gather(*tasks, return_exceptions=return_exceptions)
return tuple(results)
except Exception as e:
if attempt == max_retries:
echo_log(f"共执行 {max_retries} 次后全部失败: {str(e)}")
else:
echo_log(f"执行第 {attempt + 1} 次重试失败.")
# 指数退避
await asyncio.sleep(eba * (attempt + 1))
raise RuntimeError("Unreachable")