iOSBuildServer/backend/services/build_queue.py

88 lines
2.9 KiB
Python

"""并行打包队列管理"""
import asyncio
from typing import Dict, Callable, Optional
from datetime import datetime
from .log_streamer import log_streamer
class BuildQueue:
"""打包任务队列"""
def __init__(self, max_concurrent: int = 2):
self._max_concurrent = max_concurrent
self._semaphore = asyncio.Semaphore(max_concurrent)
self._queue: asyncio.Queue = asyncio.Queue()
self._running_tasks: Dict[str, asyncio.Task] = {}
self._workers: list = []
self._started = False
async def start(self):
"""启动工作线程"""
if self._started:
return
self._started = True
for i in range(self._max_concurrent):
worker = asyncio.create_task(self._worker(f"worker-{i}"))
self._workers.append(worker)
async def _worker(self, name: str):
"""工作协程"""
while True:
task_id, build_func = await self._queue.get()
try:
await self._semaphore.acquire()
try:
self._running_tasks[task_id] = asyncio.current_task()
await build_func(task_id)
finally:
self._running_tasks.pop(task_id, None)
self._semaphore.release()
except Exception as e:
await log_streamer.emit_error(task_id, f"任务异常: {str(e)}")
finally:
self._queue.task_done()
async def submit(self, task_id: str, build_func: Optional[Callable] = None):
"""提交打包任务"""
if not self._started:
await self.start()
if build_func is None:
from .build_service import run_build_task
build_func = run_build_task
# 在任务入队前创建日志队列,避免前端 WebSocket 先连上却无队列可订阅。
log_streamer.create_queue(task_id)
await self._queue.put((task_id, build_func))
await log_streamer.emit(task_id, "任务已加入队列,等待执行...")
def cancel(self, task_id: str):
"""取消任务"""
task = self._running_tasks.get(task_id)
if task and not task.done():
task.cancel()
def update_max_concurrent(self, new_max: int):
"""更新最大并发数(运行时生效)"""
if new_max == self._max_concurrent:
return
old = self._max_concurrent
self._max_concurrent = new_max
# 调整信号量:增加并发时 release 额外的许可,减少时由自然消费收敛
if new_max > old:
for _ in range(new_max - old):
self._semaphore.release()
@property
def queue_size(self) -> int:
return self._queue.qsize()
@property
def running_count(self) -> int:
return len(self._running_tasks)
# 全局单例
build_queue = BuildQueue()