Spaces:
Sleeping
Sleeping
File size: 2,944 Bytes
1d62d5e 4c4ed15 480abf3 1d62d5e 480abf3 1d62d5e ba4292e 1d62d5e ba4292e 1d62d5e 4c4ed15 ba4292e 4c4ed15 ba4292e 4c4ed15 1d62d5e ba4292e 1d62d5e ba4292e 1d62d5e 480abf3 1d62d5e ba4292e 1d62d5e ba4292e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | import asyncio
import threading
from typing import Any, cast
from app.core.logger import get_logger
from app.models.report import EngineeringReport
from app.services.analysis_service import run_full_analysis
from app.worker.celery_app import celery_app, HIGH_QUEUE, LOW_QUEUE
logger = get_logger(__name__)
# Repos larger than this threshold are routed to the low-priority queue
# so they do not starve small, fast jobs.
LARGE_REPO_MB_THRESHOLD = 50
def route_queue_for_repo(estimated_size_mb: float | None) -> str:
"""Return the appropriate queue name based on estimated repo size."""
if estimated_size_mb is not None and estimated_size_mb >= LARGE_REPO_MB_THRESHOLD:
return LOW_QUEUE
return HIGH_QUEUE
def submit_analysis_task(
github_url: str,
base_sha: str | None = None,
estimated_size_mb: float | None = None,
) -> Any:
"""
Submit an analysis task to the correct priority queue.
Call this instead of calling .delay() or .apply_async() directly so
that queue routing logic stays in one place.
"""
queue = route_queue_for_repo(estimated_size_mb)
logger.info(
"Submitting analysis task",
extra={"url": github_url, "queue": queue, "size_mb": estimated_size_mb},
)
return analyze_repository_task.apply_async(
args=[github_url, base_sha],
queue=queue,
)
def _run_coro_sync(coro: Any) -> Any:
"""
Run an awaitable to completion from synchronous Celery worker code.
Uses asyncio.run() when no event loop is running (normal prefork worker).
Falls back to a thread when a loop is already running (eager test mode).
"""
try:
asyncio.get_running_loop()
except RuntimeError:
return asyncio.run(coro)
result: dict[str, Any] = {}
def _runner() -> None:
result["value"] = asyncio.run(coro)
thread = threading.Thread(target=_runner)
thread.start()
thread.join()
return result["value"]
@celery_app.task(name="analyze_repository_task", bind=True, max_retries=2)
def analyze_repository_task(
self: Any,
github_url: str,
base_sha: str | None = None,
) -> dict[str, Any]:
"""
Celery task wrapper around the async analysis pipeline.
Runs the full multi-agent pipeline and returns a JSON-serialisable
EngineeringReport dict. Retries up to 2 times on unexpected failure
with exponential backoff.
"""
try:
report = cast(
EngineeringReport,
_run_coro_sync(run_full_analysis(github_url, base_sha)),
)
return cast(dict[str, Any], report.model_dump())
except Exception as exc:
logger.exception(
"Analysis task failed",
extra={"url": github_url, "queue": self.request.delivery_info.get("routing_key"), "error": str(exc)},
)
# Exponential backoff: 60s, 120s
raise self.retry(exc=exc, countdown=60 * (self.request.retries + 1)) |