This commit is contained in:
2026-03-18 17:30:13 +01:00
parent 58a233236c
commit d80d912fb3
36 changed files with 3502 additions and 4 deletions
+159
View File
@@ -0,0 +1,159 @@
"""
Entrypoint worker arq PecFlow IMAP Sync Engine.
Avvio: python -m app.main
Cosa fa:
1. Connette a Redis tramite arq
2. on_startup → avvia MailboxPool (N task IMAP asincroni)
3. on_shutdown → ferma MailboxPool
4. Registra job arq (sync_mailbox, future: send_pec, archive_batch, ecc.)
5. Loop arq per processare job dalla coda Redis
L'event loop è condiviso tra arq e MailboxPool (asyncio task).
"""
import asyncio
import logging
import sys
from typing import Any
import redis.asyncio as aioredis
from arq import run_worker
from arq.connections import RedisSettings
from app.config import get_settings
from app.imap.pool import MailboxPool
from app.jobs.sync_mailbox import sync_mailbox
from app.storage.minio_client import ensure_bucket_exists
settings = get_settings()
# Logging
logging.basicConfig(
level=getattr(logging, settings.log_level.upper(), logging.INFO),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
stream=sys.stdout,
)
logger = logging.getLogger(__name__)
# Pool globale (accessibile dalle callback)
_mailbox_pool: MailboxPool | None = None
# ─── Lifecycle callbacks arq ──────────────────────────────────────────────────
async def on_startup(ctx: dict[str, Any]) -> None:
"""
Inizializzazione worker all'avvio.
Avvia il MailboxPool con tutte le caselle attive.
"""
global _mailbox_pool
logger.info("🚀 PecFlow Worker avviato")
logger.info(f" DB: {settings.database_url.split('@')[-1]}")
logger.info(f" Redis: {settings.redis_url}")
logger.info(f" MinIO: {settings.minio_endpoint}")
# Verifica/crea bucket MinIO
try:
await ensure_bucket_exists()
except Exception as e:
logger.warning(f"MinIO non disponibile al startup: {e}")
# Crea client Redis condiviso
redis_client = aioredis.from_url(settings.redis_url, decode_responses=True)
ctx["redis"] = redis_client
# Avvia MailboxPool
_mailbox_pool = MailboxPool(redis_client=redis_client)
ctx["mailbox_pool"] = _mailbox_pool
await _mailbox_pool.start()
logger.info(
f"✅ Worker pronto: {_mailbox_pool.active_count} caselle IMAP attive"
)
async def on_shutdown(ctx: dict[str, Any]) -> None:
"""Cleanup all'arresto del worker."""
global _mailbox_pool
logger.info("🛑 PecFlow Worker in arresto...")
pool = ctx.get("mailbox_pool") or _mailbox_pool
if pool:
await pool.stop()
redis_client = ctx.get("redis")
if redis_client:
await redis_client.aclose()
logger.info("🛑 Worker fermato")
# ─── Worker health check ──────────────────────────────────────────────────────
async def health_check(ctx: dict[str, Any]) -> dict:
"""
Job speciale per health check del worker.
Può essere chiamato da monitoring esterno.
"""
pool: MailboxPool | None = ctx.get("mailbox_pool")
return {
"status": "ok",
"active_imap_connections": pool.active_count if pool else 0,
}
# ─── WorkerSettings arq ───────────────────────────────────────────────────────
def _parse_redis_settings() -> RedisSettings:
"""Parsa REDIS_URL in RedisSettings arq."""
url = settings.redis_url
# Supporta redis://host:port/db e redis://user:pass@host:port/db
import urllib.parse
parsed = urllib.parse.urlparse(url)
host = parsed.hostname or "localhost"
port = parsed.port or 6379
db = int(parsed.path.lstrip("/") or "0")
password = parsed.password or None
return RedisSettings(host=host, port=port, database=db, password=password)
class WorkerSettings:
"""Configurazione del worker arq."""
# Funzioni/job registrati
functions = [sync_mailbox, health_check]
# Callbacks lifecycle
on_startup = on_startup
on_shutdown = on_shutdown
# Redis
redis_settings = _parse_redis_settings()
# Concorrenza
max_jobs = 20
# Timeout per ogni job (secondi)
job_timeout = 300
# Retry automatico in caso di errore
max_tries = 3
# Polling interval (arq controlla la coda ogni N ms)
poll_delay = 0.5
# Keep job results per N secondi
keep_result = 3600
# ─── Entrypoint ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
logger.info("Avvio PecFlow Worker (arq)...")
run_worker(WorkerSettings)