Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.1.0 - 2026-08-13
Initial release.
Added
OxBackend, a database-backed backend for Django's Tasks framework (django.tasks, Django 6.0+). Tasks are stored in the application database; no broker required.- Transactional enqueue:
enqueue()is a single INSERT on the caller's connection, so a task enqueued insidetransaction.atomic()commits or rolls back with the business data. ox_workermanagement command: claims tasks withSELECT ... FOR UPDATE SKIP LOCKEDwhere supported (PostgreSQL, MySQL 8+) and an atomic compare-and-set UPDATE elsewhere (including SQLite). Configurable via--backend,--queues,--concurrency(thread pool),--interval, and--lock-timeout.- Retries with exponential backoff (
MAX_ATTEMPTS,BACKOFF_INITIAL,BACKOFF_MAX), keeping the full traceback of every attempt. - Reaper: tasks whose worker died are returned to the queue after
LOCK_TIMEOUTand count as a failed attempt. - Graceful drain: on SIGTERM/SIGINT the worker stops claiming, finishes in-flight tasks, then exits; a second signal forces an immediate exit.
- Priorities (-100 to 100, higher first) and deferred tasks (
run_after), with the correspondingsupports_*flags declared on the backend. - Result store:
get_result(),refresh(), and the async variants, with status, return value, and per-attempt errors readable from the database. ox_prunemanagement command: batched deletion of finished task rows (--older-than,--include-failed,--batch-size,--dry-run).django_ox.stats: read-only queue metrics as plain ORM queries, on both supported databases: per-queue status counts, backlog depth and age, throughput and failure rate over a trailing window, and time since the last task claim.ox_healthmanagement command: exits non-zero with a one-line reason when the database is unreachable or a--max-backlog,--max-ageor--worker-timeoutthreshold is breached; built for cron alerting and container probes.- Structured logging: worker lifecycle events (claim, start, success,
retry, failure, reclaim, dispatch, shutdown) log to the
django_oxlogger with stable extra keys (event,task_id,queue,attempt,duration_ms, ...) for JSON log handlers. - Recurring tasks: cron schedules declared in the
TASKSsetting (SCHEDULESoption), dispatched by the workers themselves; a unique constraint on (schedule, tick) makes each tick fire exactly once across any number of workers. Five-field cron syntax plus@hourly-style shortcuts; misconfigured schedules fail at startup and inmanage.py check. On recovery after downtime, only the latest missed tick fires. - System check
django_ox.E003: a schedule name defined on more than one backend is rejected, at worker startup and inmanage.py check, because the tick log is keyed by schedule name alone and shared names would let the backends suppress each other's ticks.
Fixed
Findings from a pre-release adversarial review:
- A tick row dated in the future (for example written by a worker with a fast clock) no longer suppresses schedule dispatch fleet-wide; ticks that are due now fire regardless, and the unique constraint still protects the future instant itself.
- Cron step values larger than the field's range (such as
*/61in the minute field, which silently collapsed to minute 0) are rejected at parse time.
Security
- A stored
task_pathis now required to resolve to adjango.tasksTask (a function registered with@task). A row naming any other importable callable is rejected as an un-runnable task instead of being executed, so the worker never invokes an arbitrary dotted path pulled from the table.SECURITY.mddocuments the full trust model, the JSON-only serialization, and the guidance to keep secrets out of task arguments. - Added an API stability and deprecation policy (
docs/stability.md): the public API surface, the pre-1.0 SemVer rule, the deprecation window, and the supported Python and Django matrix.