| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- """
- engine.queue — Job Queue abstraction (memory / Redis).
- Dev: asyncio.PriorityQueue (in-memory)
- Prod: Redis Sorted Set (ZADD score=priority*1e12+timestamp)
- """
- from __future__ import annotations
- import asyncio
- import json
- import time
- from typing import Any, Dict, Optional
- from dataclasses import dataclass, field
- from agentpaas.db.models import gen_id, now_utc
- @dataclass
- class Job:
- id: str = ""
- tenant_id: str = ""
- agent_id: str = ""
- agent_version: int = 1
- config: Dict[str, Any] = field(default_factory=dict)
- input: str = ""
- priority: int = 5
- status: str = "queued"
- worker_id: str = ""
- attempts: int = 0
- max_attempts: int = 3
- result: str = ""
- error: str = ""
- created_at: str = ""
- def __post_init__(self):
- if not self.id:
- self.id = gen_id("job_")
- if not self.created_at:
- self.created_at = now_utc()
- class MemoryQueue:
- """In-memory priority queue for development."""
- def __init__(self):
- self._queue: asyncio.PriorityQueue = asyncio.PriorityQueue()
- self._jobs: Dict[str, Job] = {}
- async def enqueue(self, job: Job) -> str:
- self._jobs[job.id] = job
- score = job.priority * 1_000_000_000_000 + time.time()
- await self._queue.put((score, job.id))
- return job.id
- async def dequeue(self, timeout: float = 30.0) -> Optional[Job]:
- try:
- _, job_id = await asyncio.wait_for(self._queue.get(), timeout=timeout)
- job = self._jobs.get(job_id)
- if job:
- job.status = "running"
- return job
- except asyncio.TimeoutError:
- return None
- def get_job(self, job_id: str) -> Optional[Job]:
- return self._jobs.get(job_id)
- def update_job(self, job_id: str, **kwargs):
- job = self._jobs.get(job_id)
- if job:
- for k, v in kwargs.items():
- setattr(job, k, v)
- @property
- def depth(self) -> int:
- return self._queue.qsize()
- class RedisQueue:
- """Redis-backed priority queue for production."""
- def __init__(self, redis_url: str, prefix: str = "agentpaas:queue:"):
- import redis
- self.r = redis.from_url(redis_url)
- self.prefix = prefix
- self.queue_key = f"{prefix}jobs"
- self.data_key = f"{prefix}data:"
- async def enqueue(self, job: Job) -> str:
- data = json.dumps({
- "id": job.id, "tenant_id": job.tenant_id, "agent_id": job.agent_id,
- "agent_version": job.agent_version, "config": job.config,
- "input": job.input, "priority": job.priority, "status": "queued",
- "max_attempts": job.max_attempts, "created_at": job.created_at,
- })
- score = job.priority * 1_000_000_000_000 + time.time()
- self.r.zadd(self.queue_key, {job.id: score})
- self.r.set(f"{self.data_key}{job.id}", data, ex=86400)
- return job.id
- async def dequeue(self, timeout: float = 30.0) -> Optional[Job]:
- result = self.r.zpopmin(self.queue_key, count=1)
- if not result:
- return None
- job_id = result[0][0].decode() if isinstance(result[0][0], bytes) else result[0][0]
- data = self.r.get(f"{self.data_key}{job_id}")
- if not data:
- return None
- d = json.loads(data)
- job = Job(**{k: v for k, v in d.items() if k in Job.__dataclass_fields__})
- job.status = "running"
- self.r.set(f"{self.data_key}{job_id}", json.dumps({**d, "status": "running"}), ex=86400)
- return job
- def get_job(self, job_id: str) -> Optional[Job]:
- data = self.r.get(f"{self.data_key}{job_id}")
- if not data:
- return None
- d = json.loads(data)
- return Job(**{k: v for k, v in d.items() if k in Job.__dataclass_fields__})
- def update_job(self, job_id: str, **kwargs):
- data = self.r.get(f"{self.data_key}{job_id}")
- if data:
- d = json.loads(data)
- d.update(kwargs)
- self.r.set(f"{self.data_key}{job_id}", json.dumps(d), ex=86400)
- @property
- def depth(self) -> int:
- return self.r.zcard(self.queue_key)
- def create_queue(redis_url: Optional[str] = None):
- """Factory: Redis if URL provided, else in-memory."""
- if redis_url:
- try:
- return RedisQueue(redis_url)
- except Exception:
- pass
- return MemoryQueue()
|