"""JobQueue — Semaphore 기반 동시성 제한.""" from __future__ import annotations import asyncio import logging from services import worker from services.job_manager import Job, job_manager from services.state_stream import state_stream logger = logging.getLogger(__name__) class JobQueue: def __init__(self, max_concurrent: int = 3) -> None: self._semaphore = asyncio.Semaphore(max_concurrent) self._waiting: list[str] = [] # 대기 중 job_id (순서 보장) self._active: set[str] = set() async def submit(self, job: Job) -> asyncio.Task: task = asyncio.create_task(self._run_with_semaphore(job)) job_manager.attach_task(job.id, task) return task async def _run_with_semaphore(self, job: Job) -> None: self._waiting.append(job.id) pos = self.position(job.id) if pos and pos > 0: await state_stream.push(job.id, "queued", {"position": pos}) try: async with self._semaphore: self._waiting.remove(job.id) self._active.add(job.id) await worker.run(job) finally: self._active.discard(job.id) def position(self, job_id: str) -> int | None: try: return self._waiting.index(job_id) + 1 except ValueError: return None @property def stats(self) -> dict: return {"pending": len(self._waiting), "active": len(self._active)} job_queue: JobQueue | None = None def init_queue(max_concurrent: int = 3) -> JobQueue: global job_queue job_queue = JobQueue(max_concurrent) return job_queue