e30f50e6fe
Plugs the FC long-running-entity discipline gaps the 2026-06-02 audit flagged: every entity that can get stuck now has recovery + retention + timeout, and the long-runners no longer collide with the FC-3i sweep. Recovery sweeps (every 5 min): - recover_stalled_backup_runs — flips BackupRun stuck in running/restoring past 7h (covers the 6.5h images-backup hard limit) to error. prune_backups docstring corrected — the FC-3i TaskRun sweep never touched BackupRun rows. - recover_stalled_library_audit_runs — flips LibraryAuditRun stuck past 135 min (10-min buffer above scan_library_for_rule's 2h5m hard limit) to error. Previously a SIGKILL'd row blocked all future audits until manual DB surgery. - recover_stalled_import_batches — finalizes ImportBatch rows stuck running >2h whose child tasks are all terminal (orphan case where the orchestrator crashed before the closing UPDATE). Uses the same EXISTS predicate /api/system/stats already had. Retention (daily): - prune_library_audit_runs — 30-day window. Audit rows carry matched_ids JSONB blobs that can hold tens of thousands of ids. - prune_import_batches — 30-day window. Cascades to ImportTask via the model relationship. time_limits on five long-runners that previously had none (the audit's headline finding — every one of these collided with the recover_stalled_task_runs 5-min default and could be marked 'error' mid-flight): - scan_directory: 60m soft / 70m hard - verify_integrity: 60m / 70m - backfill_phash: 30m / 35m - apply_allowlist_tags: 30m / 35m - recompute_centroids: 30m / 35m QUEUE_STUCK_THRESHOLD_MINUTES now covers maintenance (75) and scan (75) — above the longest task on each — with per-task overrides for the outliers (backup_images_task 420, restore_images_task 420, scan_library_for_rule 130). start_audit_run guard is now age-aware: a 'running' row older than the audit hard limit doesn't block a new run (the sweep will catch it within 5 min). Previously a SIGKILL'd row blocked forever. /api/import/status now uses the same EXISTS predicate /api/system/stats does, so the two endpoints no longer disagree on the active-batch question. DownloadEvent.started_at resets on pending→running so a freshly- promoted event from a busy queue isn't measured against its original enqueue time (was racing recover_stalled_download_events on heavy-queue days).
143 lines
5.8 KiB
Python
143 lines
5.8 KiB
Python
"""Celery configuration with separate queue lanes.
|
|
|
|
Queues:
|
|
import — filesystem import path (FC-2)
|
|
ml — WD14 + SigLIP inference (FC-2; runs in the ml-worker image)
|
|
thumbnail — image and video thumbnail generation (FC-2)
|
|
download — gallery-dl tasks (FC-3)
|
|
scan — periodic source checks (FC-3) — kept separate so long imports
|
|
don't starve the scheduler
|
|
maintenance — pHash recomputation, centroid rebuild, etc. (FC-2/FC-3)
|
|
default — anything not explicitly routed
|
|
"""
|
|
|
|
from celery import Celery
|
|
|
|
from .config import get_config
|
|
|
|
|
|
def make_celery() -> Celery:
|
|
cfg = get_config()
|
|
app = Celery(
|
|
"fabledcurator",
|
|
broker=cfg.celery_broker_url,
|
|
backend=cfg.celery_result_backend,
|
|
include=[
|
|
"backend.app.tasks.smoke",
|
|
"backend.app.tasks.scan",
|
|
"backend.app.tasks.import_file",
|
|
"backend.app.tasks.thumbnail",
|
|
"backend.app.tasks.maintenance",
|
|
"backend.app.tasks.ml",
|
|
"backend.app.tasks.download",
|
|
"backend.app.tasks.backup",
|
|
"backend.app.tasks.admin",
|
|
"backend.app.tasks.library_audit",
|
|
],
|
|
)
|
|
app.conf.update(
|
|
task_default_queue="default",
|
|
task_routes={
|
|
"backend.app.tasks.import_file.*": {"queue": "import"},
|
|
"backend.app.tasks.ml.*": {"queue": "ml"},
|
|
"backend.app.tasks.thumbnail.*": {"queue": "thumbnail"},
|
|
"backend.app.tasks.download.*": {"queue": "download"},
|
|
"backend.app.tasks.scan.*": {"queue": "scan"},
|
|
"backend.app.tasks.maintenance.*": {"queue": "maintenance"},
|
|
"backend.app.tasks.backup.*": {"queue": "maintenance"},
|
|
"backend.app.tasks.admin.*": {"queue": "maintenance"},
|
|
"backend.app.tasks.library_audit.*": {"queue": "maintenance"},
|
|
},
|
|
# Heavy ML tasks need fair dispatch — see ImageRepo's precedent.
|
|
task_acks_late=True,
|
|
worker_prefetch_multiplier=1,
|
|
broker_connection_retry_on_startup=True,
|
|
beat_schedule={
|
|
"recover-interrupted-tasks": {
|
|
"task": "backend.app.tasks.maintenance.recover_interrupted_tasks",
|
|
"schedule": 300.0, # every 5 minutes
|
|
},
|
|
"cleanup-old-tasks": {
|
|
"task": "backend.app.tasks.maintenance.cleanup_old_tasks",
|
|
"schedule": 86400.0, # daily
|
|
},
|
|
"ml-backfill-daily": {
|
|
"task": "backend.app.tasks.ml.backfill",
|
|
"schedule": 86400.0,
|
|
},
|
|
"recompute-centroids-daily": {
|
|
"task": "backend.app.tasks.ml.recompute_centroids",
|
|
"schedule": 86400.0,
|
|
},
|
|
"apply-allowlist-sweep-daily": {
|
|
"task": "backend.app.tasks.ml.apply_allowlist_tags",
|
|
"schedule": 86400.0,
|
|
},
|
|
"integrity-verify-weekly": {
|
|
"task": "backend.app.tasks.maintenance.verify_integrity",
|
|
"schedule": 604800.0, # weekly
|
|
},
|
|
"fc3d-tick-due-sources": {
|
|
"task": "backend.app.tasks.scan.tick_due_sources",
|
|
"schedule": 60.0, # every minute
|
|
},
|
|
"fc3d-cleanup-download-events": {
|
|
"task": "backend.app.tasks.maintenance.cleanup_old_download_events",
|
|
"schedule": 86400.0, # daily
|
|
},
|
|
"recover-stalled-download-events": {
|
|
"task": "backend.app.tasks.maintenance.recover_stalled_download_events",
|
|
"schedule": 300.0, # every 5 min, matches recover-interrupted-tasks
|
|
},
|
|
"recover-stalled-task-runs": {
|
|
"task": "backend.app.tasks.maintenance.recover_stalled_task_runs",
|
|
"schedule": 300.0, # every 5 min, matches recover-interrupted-tasks
|
|
},
|
|
"prune-task-runs": {
|
|
"task": "backend.app.tasks.maintenance.prune_task_runs",
|
|
"schedule": 86400.0, # daily
|
|
},
|
|
"fc3h-backup-db-nightly": {
|
|
"task": "backend.app.tasks.backup.backup_db_nightly",
|
|
"schedule": 3600.0, # hourly tick; task self-gates on configured UTC hour
|
|
},
|
|
"fc3h-prune-backups": {
|
|
"task": "backend.app.tasks.backup.prune_backups",
|
|
"schedule": 86400.0, # daily
|
|
},
|
|
# Audit 2026-06-02 — three new per-entity recovery sweeps.
|
|
# Each runs every 5 min like the other recover_stalled_*
|
|
# sweeps; each is a no-op when nothing is stuck.
|
|
"recover-stalled-backup-runs": {
|
|
"task": "backend.app.tasks.maintenance.recover_stalled_backup_runs",
|
|
"schedule": 300.0,
|
|
},
|
|
"recover-stalled-library-audit-runs": {
|
|
"task": "backend.app.tasks.maintenance.recover_stalled_library_audit_runs",
|
|
"schedule": 300.0,
|
|
},
|
|
"recover-stalled-import-batches": {
|
|
"task": "backend.app.tasks.maintenance.recover_stalled_import_batches",
|
|
"schedule": 300.0,
|
|
},
|
|
# Audit 2026-06-02 — daily retention for two entities
|
|
# whose terminal rows otherwise accumulate forever.
|
|
"prune-library-audit-runs": {
|
|
"task": "backend.app.tasks.maintenance.prune_library_audit_runs",
|
|
"schedule": 86400.0,
|
|
},
|
|
"prune-import-batches": {
|
|
"task": "backend.app.tasks.maintenance.prune_import_batches",
|
|
"schedule": 86400.0,
|
|
},
|
|
},
|
|
timezone="UTC",
|
|
)
|
|
# FC-3i: register task_run signal handlers (side-effect import).
|
|
from . import celery_signals # noqa: F401
|
|
|
|
return app
|
|
|
|
|
|
celery = make_celery()
|