dj-queue 0.2.4__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dj_queue-0.2.4 → dj_queue-0.4.0}/PKG-INFO +180 -10
- {dj_queue-0.2.4 → dj_queue-0.4.0}/README.md +179 -9
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/admin.py +133 -48
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/config.py +17 -1
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/dashboard.py +276 -184
- dj_queue-0.4.0/dj_queue/management/commands/dj_queue_prune.py +44 -0
- dj_queue-0.4.0/dj_queue/operations/cleanup.py +103 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/operations/jobs.py +28 -2
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/base.py +39 -13
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/notify.py +2 -2
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/scheduler.py +32 -12
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/supervisor.py +30 -5
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +14 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +13 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +23 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +9 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_paginator.html +19 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_queue_controls.html +18 -0
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +13 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/templates/admin/dj_queue/change_form.html +6 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/templates/admin/dj_queue/dashboard.html +9 -279
- dj_queue-0.4.0/dj_queue/templates/admin/dj_queue/includes/fieldset.html +43 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +14 -48
- dj_queue-0.4.0/dj_queue/templatetags/__init__.py +0 -0
- dj_queue-0.4.0/dj_queue/templatetags/dj_queue_admin.py +45 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/pyproject.toml +1 -1
- dj_queue-0.2.4/dj_queue/management/commands/dj_queue_prune.py +0 -22
- dj_queue-0.2.4/dj_queue/operations/cleanup.py +0 -37
- {dj_queue-0.2.4 → dj_queue-0.4.0}/LICENSE +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/api.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/backend.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/db.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/log.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/operations/concurrency.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.2.4 → dj_queue-0.4.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Database-backed task queue backend for Django's django.tasks framework
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -165,9 +165,11 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
165
165
|
- backend-aware dashboard and raw changelists
|
|
166
166
|
- queue controls: pause, resume, clear ready
|
|
167
167
|
- job detail action: enqueue a fresh copy of any stored job
|
|
168
|
+
- recurring-task actions: unschedule from list and detail views
|
|
168
169
|
- pause detail action: resume the paused queue from the raw pause row
|
|
169
170
|
- failed-job actions: retry and discard from list and detail views
|
|
170
171
|
- queue drill-down pages for state-specific inspection
|
|
172
|
+
- queue drill-down actions: discard ready, scheduled, and blocked jobs; retry or discard failed jobs; enqueue finished jobs again
|
|
171
173
|
|
|
172
174
|
**Dashboard overview**
|
|
173
175
|
|
|
@@ -217,8 +219,9 @@ results = process_item.get_backend().enqueue_all(
|
|
|
217
219
|
|
|
218
220
|
### Enqueue after commit
|
|
219
221
|
|
|
220
|
-
`enqueue()` writes immediately
|
|
221
|
-
the current transaction, use
|
|
222
|
+
`enqueue()` writes immediately and returns a real persisted task result ID. If a
|
|
223
|
+
task depends on rows that are still inside the current transaction, use
|
|
224
|
+
`enqueue_on_commit()`:
|
|
222
225
|
|
|
223
226
|
```python
|
|
224
227
|
from django.db import transaction
|
|
@@ -230,6 +233,10 @@ with transaction.atomic():
|
|
|
230
233
|
enqueue_on_commit(send_receipt, order.id)
|
|
231
234
|
```
|
|
232
235
|
|
|
236
|
+
`dj_queue` does not defer inserts implicitly or return placeholder result IDs for
|
|
237
|
+
uncommitted work. Use the helper above or `transaction.on_commit()` directly
|
|
238
|
+
when the job must not exist before commit.
|
|
239
|
+
|
|
233
240
|
### Examples
|
|
234
241
|
|
|
235
242
|
The repository ships real runnable examples in `examples/`.
|
|
@@ -274,14 +281,18 @@ python manage.py dj_queue --skip-recurring
|
|
|
274
281
|
Mode and topology notes:
|
|
275
282
|
|
|
276
283
|
- `fork` is the default standalone mode
|
|
277
|
-
- `async` runs supervised actors in threads inside one process
|
|
284
|
+
- `async` is also supported as a standalone mode and runs supervised actors in threads inside one process
|
|
278
285
|
- `--only-work` starts workers without dispatchers or scheduler
|
|
279
286
|
- `--only-dispatch` starts dispatchers without workers or scheduler
|
|
280
287
|
- `--skip-recurring` starts without the scheduler
|
|
281
288
|
|
|
282
289
|
`fork` runs each worker, dispatcher, and scheduler as a separate OS process.
|
|
283
290
|
`async` runs them as threads in one process, i.e., lower memory, less isolation.
|
|
284
|
-
Default is `fork`. Use `async`
|
|
291
|
+
Default is `fork`. Use standalone `async` when you want one-process supervision
|
|
292
|
+
with lower memory use and less isolation, or embedded `async` when `dj_queue`
|
|
293
|
+
should live inside an ASGI or Gunicorn server process.
|
|
294
|
+
|
|
295
|
+
In `async` mode, worker `processes > 1` is ignored and normalized to `1`.
|
|
285
296
|
|
|
286
297
|
### Claiming order
|
|
287
298
|
|
|
@@ -294,6 +305,29 @@ For example, a worker configured with `queues: ["email", "default"]` will
|
|
|
294
305
|
prefer ready work from `email` before `default`, even if `default` contains
|
|
295
306
|
higher-priority rows.
|
|
296
307
|
|
|
308
|
+
If you combine queue order with priorities, queue selector order still wins
|
|
309
|
+
across queues. Prefer one primary scheduling mechanism per worker when you can.
|
|
310
|
+
|
|
311
|
+
### Signals and recovery
|
|
312
|
+
|
|
313
|
+
In standalone mode, both `fork` and `async` `python manage.py dj_queue`
|
|
314
|
+
supervisors own runtime signal handling:
|
|
315
|
+
|
|
316
|
+
- `SIGTERM` and `SIGINT` request graceful shutdown
|
|
317
|
+
- `SIGQUIT` takes the immediate hard-exit path
|
|
318
|
+
- `shutdown_timeout` controls how long the runtime waits for in-flight work to drain
|
|
319
|
+
- `supervisor_pidfile` can prevent duplicate standalone supervisors on one host
|
|
320
|
+
|
|
321
|
+
Runners heartbeat into the queue database. If claimed work is left behind,
|
|
322
|
+
`dj_queue` preserves it as failed work that operators can inspect and retry:
|
|
323
|
+
|
|
324
|
+
- `ProcessExitError`: a supervised runner exited unexpectedly
|
|
325
|
+
- `ProcessPrunedError`: a runner heartbeat expired and the process was pruned
|
|
326
|
+
- `ProcessMissingError`: claimed work was found without its registered process
|
|
327
|
+
|
|
328
|
+
Use `python manage.py dj_queue_health` to check whether any fresh runtime
|
|
329
|
+
process rows exist for a backend.
|
|
330
|
+
|
|
297
331
|
## Database Support
|
|
298
332
|
|
|
299
333
|
| Backend | Support level | Notes |
|
|
@@ -318,6 +352,31 @@ must be JSON round-trippable.
|
|
|
318
352
|
If you need to pass model instances, files, or custom objects, store them
|
|
319
353
|
elsewhere and pass identifiers or serialized data instead.
|
|
320
354
|
|
|
355
|
+
## Errors When Enqueuing
|
|
356
|
+
|
|
357
|
+
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
358
|
+
backend-side validation failures instead of silently dropping work.
|
|
359
|
+
|
|
360
|
+
Common reasons include:
|
|
361
|
+
|
|
362
|
+
- args or kwargs are not JSON round-trippable
|
|
363
|
+
- `concurrency_key` is set without `concurrency_limit`
|
|
364
|
+
- `concurrency_key` cannot be resolved from the enqueue arguments
|
|
365
|
+
- `concurrency_key` does not resolve to a non-empty string up to 255 chars
|
|
366
|
+
- `on_conflict` is not `"block"` or `"discard"`
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
from dj_queue.exceptions import EnqueueError
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
sync_account.enqueue(account_id, "refresh")
|
|
373
|
+
except EnqueueError as exc:
|
|
374
|
+
handle_enqueue_error(exc)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Task execution errors are different: they become failed jobs and stay
|
|
378
|
+
inspectable in the queue database.
|
|
379
|
+
|
|
321
380
|
## Recurring Tasks
|
|
322
381
|
|
|
323
382
|
`dj_queue` supports both static recurring tasks from settings and dynamic
|
|
@@ -373,6 +432,16 @@ config.
|
|
|
373
432
|
The scheduler is part of the normal `dj_queue` runtime. You do not run a
|
|
374
433
|
separate recurring service.
|
|
375
434
|
|
|
435
|
+
Recurring notes:
|
|
436
|
+
|
|
437
|
+
- schedules are cron expressions
|
|
438
|
+
- only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
|
|
439
|
+
- Django admin exposes the same unschedule operation on recurring-task list and detail views
|
|
440
|
+
- multiple schedulers sharing the same recurring config dedupe firing in the database
|
|
441
|
+
- finished-job cleanup runs as internal scheduler maintenance when `preserve_finished_jobs=True` and `clear_finished_jobs_after` is set
|
|
442
|
+
- failed-job cleanup can run as internal scheduler maintenance when `clear_failed_jobs_after` is set
|
|
443
|
+
- recurring execution reservation cleanup can run as internal scheduler maintenance when `clear_recurring_executions_after` is set
|
|
444
|
+
|
|
376
445
|
## Concurrency Controls
|
|
377
446
|
|
|
378
447
|
Tasks can opt into database-backed concurrency limits.
|
|
@@ -418,15 +487,31 @@ orders.resume()
|
|
|
418
487
|
orders.clear()
|
|
419
488
|
```
|
|
420
489
|
|
|
490
|
+
Queue control notes:
|
|
491
|
+
|
|
492
|
+
- pausing a queue stops future claims, not enqueueing or already-claimed work
|
|
493
|
+
- `clear()` discards ready jobs only
|
|
494
|
+
- pass `backend_alias=` when you want to target a non-default `TASKS` alias
|
|
495
|
+
|
|
421
496
|
Operational commands:
|
|
422
497
|
|
|
423
498
|
```bash
|
|
424
499
|
python manage.py dj_queue_health
|
|
425
500
|
python manage.py dj_queue_health --max-age 120
|
|
426
501
|
python manage.py dj_queue_prune --older-than 86400
|
|
502
|
+
python manage.py dj_queue_prune --failed-older-than 604800
|
|
503
|
+
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
427
504
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
505
|
+
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
428
506
|
```
|
|
429
507
|
|
|
508
|
+
The runtime, health, and prune commands all accept `--backend` to target a
|
|
509
|
+
non-default backend alias.
|
|
510
|
+
|
|
511
|
+
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
512
|
+
task import path, while `--task-key` filters recurring execution cleanup by
|
|
513
|
+
recurring task key.
|
|
514
|
+
|
|
430
515
|
## Failed Jobs
|
|
431
516
|
|
|
432
517
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
@@ -443,6 +528,25 @@ retry_failed_job(job_id)
|
|
|
443
528
|
discard_failed_job(job_id)
|
|
444
529
|
```
|
|
445
530
|
|
|
531
|
+
Model helpers are available too:
|
|
532
|
+
|
|
533
|
+
```python
|
|
534
|
+
from dj_queue.exceptions import UndiscardableError
|
|
535
|
+
from dj_queue.models import ClaimedExecution, FailedExecution
|
|
536
|
+
|
|
537
|
+
failed = FailedExecution.objects.get(job_id=job_id)
|
|
538
|
+
failed.retry()
|
|
539
|
+
failed.discard()
|
|
540
|
+
|
|
541
|
+
FailedExecution.retry_all(FailedExecution.objects.order_by("job_id"))
|
|
542
|
+
FailedExecution.discard_all_in_batches()
|
|
543
|
+
|
|
544
|
+
try:
|
|
545
|
+
ClaimedExecution.discard_all_in_batches()
|
|
546
|
+
except UndiscardableError:
|
|
547
|
+
pass
|
|
548
|
+
```
|
|
549
|
+
|
|
446
550
|
Failures stay inspectable until you act on them.
|
|
447
551
|
|
|
448
552
|
## Multi-Database Setup
|
|
@@ -557,12 +661,19 @@ Start with these options:
|
|
|
557
661
|
- `dispatchers`: scheduled promotion and concurrency maintenance settings
|
|
558
662
|
- `scheduler`: dynamic recurring polling settings
|
|
559
663
|
- `database_alias`: database alias for queue tables and runtime activity
|
|
560
|
-
- `preserve_finished_jobs` and `clear_finished_jobs_after`: result retention and cleanup
|
|
664
|
+
- `preserve_finished_jobs` and `clear_finished_jobs_after`: successful result retention and cleanup
|
|
665
|
+
- `clear_failed_jobs_after`: optional failed-job retention window
|
|
666
|
+
- `clear_recurring_executions_after`: optional recurring reservation retention window
|
|
561
667
|
|
|
562
|
-
Additional operational tuning is available when needed
|
|
563
|
-
|
|
564
|
-
`
|
|
565
|
-
`
|
|
668
|
+
Additional operational tuning is available when needed:
|
|
669
|
+
|
|
670
|
+
- `use_skip_locked`: use `SKIP LOCKED` when the active backend supports it
|
|
671
|
+
- `listen_notify`: PostgreSQL-only worker wakeup optimization layered on top of polling
|
|
672
|
+
- `silence_polling`: suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger
|
|
673
|
+
- `process_heartbeat_interval` and `process_alive_threshold`: process liveness reporting and stale-runner detection
|
|
674
|
+
- `shutdown_timeout`: graceful drain window before standalone shutdown gives up on waiting
|
|
675
|
+
- `supervisor_pidfile`: optional pidfile guard for standalone supervisors
|
|
676
|
+
- `on_thread_error`: dotted callback path for runtime infrastructure exceptions
|
|
566
677
|
|
|
567
678
|
On PostgreSQL, `listen_notify` uses the same Django PostgreSQL driver
|
|
568
679
|
configuration as the main database connection. Install a compatible driver in
|
|
@@ -596,6 +707,8 @@ mode: async
|
|
|
596
707
|
database_alias: queue
|
|
597
708
|
preserve_finished_jobs: true
|
|
598
709
|
clear_finished_jobs_after: 86400
|
|
710
|
+
clear_failed_jobs_after: null
|
|
711
|
+
clear_recurring_executions_after: null
|
|
599
712
|
listen_notify: true
|
|
600
713
|
silence_polling: true
|
|
601
714
|
|
|
@@ -633,6 +746,63 @@ Environment overrides currently supported by `dj_queue` itself:
|
|
|
633
746
|
- `DJ_QUEUE_MODE`
|
|
634
747
|
- `DJ_QUEUE_SKIP_RECURRING`
|
|
635
748
|
|
|
749
|
+
## Lifecycle Hooks
|
|
750
|
+
|
|
751
|
+
Register hooks before starting the runtime, typically during Django startup.
|
|
752
|
+
Each callback receives the live supervisor or runner instance.
|
|
753
|
+
|
|
754
|
+
```python
|
|
755
|
+
from dj_queue.hooks import on_start, on_worker_start, register_hook
|
|
756
|
+
|
|
757
|
+
@on_start
|
|
758
|
+
def supervisor_started(process):
|
|
759
|
+
print(process.name)
|
|
760
|
+
|
|
761
|
+
@on_worker_start
|
|
762
|
+
def worker_started(process):
|
|
763
|
+
print(process.metadata)
|
|
764
|
+
|
|
765
|
+
@register_hook("scheduler.exit")
|
|
766
|
+
def scheduler_exited(process):
|
|
767
|
+
print(process.name)
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
Available hook helpers:
|
|
771
|
+
|
|
772
|
+
- supervisor: `on_start`, `on_stop`, `on_exit`
|
|
773
|
+
- worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
|
|
774
|
+
- dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
|
|
775
|
+
- scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
|
|
776
|
+
- generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
|
|
777
|
+
|
|
778
|
+
Hook notes:
|
|
779
|
+
|
|
780
|
+
- hooks fire in registration order
|
|
781
|
+
- hook failures do not block later hooks
|
|
782
|
+
- hook failures are isolated and routed through `on_thread_error`
|
|
783
|
+
|
|
784
|
+
### Runtime infrastructure errors
|
|
785
|
+
|
|
786
|
+
Set `on_thread_error` to a dotted callable path when you want custom handling
|
|
787
|
+
for queue-runtime exceptions:
|
|
788
|
+
|
|
789
|
+
```python
|
|
790
|
+
TASKS = {
|
|
791
|
+
"default": {
|
|
792
|
+
"BACKEND": "dj_queue.backend.DjQueueBackend",
|
|
793
|
+
"QUEUES": [],
|
|
794
|
+
"OPTIONS": {
|
|
795
|
+
"on_thread_error": "myapp.queue.report_runtime_error",
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
}
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
The callback receives the raised exception object for background runtime issues
|
|
802
|
+
such as hook failures, heartbeat failures, notify-watcher failures, and managed
|
|
803
|
+
runner crashes. It is not used for exceptions raised by your task code; those
|
|
804
|
+
become failed jobs instead.
|
|
805
|
+
|
|
636
806
|
## License
|
|
637
807
|
|
|
638
808
|
MIT
|
|
@@ -139,9 +139,11 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
139
139
|
- backend-aware dashboard and raw changelists
|
|
140
140
|
- queue controls: pause, resume, clear ready
|
|
141
141
|
- job detail action: enqueue a fresh copy of any stored job
|
|
142
|
+
- recurring-task actions: unschedule from list and detail views
|
|
142
143
|
- pause detail action: resume the paused queue from the raw pause row
|
|
143
144
|
- failed-job actions: retry and discard from list and detail views
|
|
144
145
|
- queue drill-down pages for state-specific inspection
|
|
146
|
+
- queue drill-down actions: discard ready, scheduled, and blocked jobs; retry or discard failed jobs; enqueue finished jobs again
|
|
145
147
|
|
|
146
148
|
**Dashboard overview**
|
|
147
149
|
|
|
@@ -191,8 +193,9 @@ results = process_item.get_backend().enqueue_all(
|
|
|
191
193
|
|
|
192
194
|
### Enqueue after commit
|
|
193
195
|
|
|
194
|
-
`enqueue()` writes immediately
|
|
195
|
-
the current transaction, use
|
|
196
|
+
`enqueue()` writes immediately and returns a real persisted task result ID. If a
|
|
197
|
+
task depends on rows that are still inside the current transaction, use
|
|
198
|
+
`enqueue_on_commit()`:
|
|
196
199
|
|
|
197
200
|
```python
|
|
198
201
|
from django.db import transaction
|
|
@@ -204,6 +207,10 @@ with transaction.atomic():
|
|
|
204
207
|
enqueue_on_commit(send_receipt, order.id)
|
|
205
208
|
```
|
|
206
209
|
|
|
210
|
+
`dj_queue` does not defer inserts implicitly or return placeholder result IDs for
|
|
211
|
+
uncommitted work. Use the helper above or `transaction.on_commit()` directly
|
|
212
|
+
when the job must not exist before commit.
|
|
213
|
+
|
|
207
214
|
### Examples
|
|
208
215
|
|
|
209
216
|
The repository ships real runnable examples in `examples/`.
|
|
@@ -248,14 +255,18 @@ python manage.py dj_queue --skip-recurring
|
|
|
248
255
|
Mode and topology notes:
|
|
249
256
|
|
|
250
257
|
- `fork` is the default standalone mode
|
|
251
|
-
- `async` runs supervised actors in threads inside one process
|
|
258
|
+
- `async` is also supported as a standalone mode and runs supervised actors in threads inside one process
|
|
252
259
|
- `--only-work` starts workers without dispatchers or scheduler
|
|
253
260
|
- `--only-dispatch` starts dispatchers without workers or scheduler
|
|
254
261
|
- `--skip-recurring` starts without the scheduler
|
|
255
262
|
|
|
256
263
|
`fork` runs each worker, dispatcher, and scheduler as a separate OS process.
|
|
257
264
|
`async` runs them as threads in one process, i.e., lower memory, less isolation.
|
|
258
|
-
Default is `fork`. Use `async`
|
|
265
|
+
Default is `fork`. Use standalone `async` when you want one-process supervision
|
|
266
|
+
with lower memory use and less isolation, or embedded `async` when `dj_queue`
|
|
267
|
+
should live inside an ASGI or Gunicorn server process.
|
|
268
|
+
|
|
269
|
+
In `async` mode, worker `processes > 1` is ignored and normalized to `1`.
|
|
259
270
|
|
|
260
271
|
### Claiming order
|
|
261
272
|
|
|
@@ -268,6 +279,29 @@ For example, a worker configured with `queues: ["email", "default"]` will
|
|
|
268
279
|
prefer ready work from `email` before `default`, even if `default` contains
|
|
269
280
|
higher-priority rows.
|
|
270
281
|
|
|
282
|
+
If you combine queue order with priorities, queue selector order still wins
|
|
283
|
+
across queues. Prefer one primary scheduling mechanism per worker when you can.
|
|
284
|
+
|
|
285
|
+
### Signals and recovery
|
|
286
|
+
|
|
287
|
+
In standalone mode, both `fork` and `async` `python manage.py dj_queue`
|
|
288
|
+
supervisors own runtime signal handling:
|
|
289
|
+
|
|
290
|
+
- `SIGTERM` and `SIGINT` request graceful shutdown
|
|
291
|
+
- `SIGQUIT` takes the immediate hard-exit path
|
|
292
|
+
- `shutdown_timeout` controls how long the runtime waits for in-flight work to drain
|
|
293
|
+
- `supervisor_pidfile` can prevent duplicate standalone supervisors on one host
|
|
294
|
+
|
|
295
|
+
Runners heartbeat into the queue database. If claimed work is left behind,
|
|
296
|
+
`dj_queue` preserves it as failed work that operators can inspect and retry:
|
|
297
|
+
|
|
298
|
+
- `ProcessExitError`: a supervised runner exited unexpectedly
|
|
299
|
+
- `ProcessPrunedError`: a runner heartbeat expired and the process was pruned
|
|
300
|
+
- `ProcessMissingError`: claimed work was found without its registered process
|
|
301
|
+
|
|
302
|
+
Use `python manage.py dj_queue_health` to check whether any fresh runtime
|
|
303
|
+
process rows exist for a backend.
|
|
304
|
+
|
|
271
305
|
## Database Support
|
|
272
306
|
|
|
273
307
|
| Backend | Support level | Notes |
|
|
@@ -292,6 +326,31 @@ must be JSON round-trippable.
|
|
|
292
326
|
If you need to pass model instances, files, or custom objects, store them
|
|
293
327
|
elsewhere and pass identifiers or serialized data instead.
|
|
294
328
|
|
|
329
|
+
## Errors When Enqueuing
|
|
330
|
+
|
|
331
|
+
`DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
|
|
332
|
+
backend-side validation failures instead of silently dropping work.
|
|
333
|
+
|
|
334
|
+
Common reasons include:
|
|
335
|
+
|
|
336
|
+
- args or kwargs are not JSON round-trippable
|
|
337
|
+
- `concurrency_key` is set without `concurrency_limit`
|
|
338
|
+
- `concurrency_key` cannot be resolved from the enqueue arguments
|
|
339
|
+
- `concurrency_key` does not resolve to a non-empty string up to 255 chars
|
|
340
|
+
- `on_conflict` is not `"block"` or `"discard"`
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from dj_queue.exceptions import EnqueueError
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
sync_account.enqueue(account_id, "refresh")
|
|
347
|
+
except EnqueueError as exc:
|
|
348
|
+
handle_enqueue_error(exc)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Task execution errors are different: they become failed jobs and stay
|
|
352
|
+
inspectable in the queue database.
|
|
353
|
+
|
|
295
354
|
## Recurring Tasks
|
|
296
355
|
|
|
297
356
|
`dj_queue` supports both static recurring tasks from settings and dynamic
|
|
@@ -347,6 +406,16 @@ config.
|
|
|
347
406
|
The scheduler is part of the normal `dj_queue` runtime. You do not run a
|
|
348
407
|
separate recurring service.
|
|
349
408
|
|
|
409
|
+
Recurring notes:
|
|
410
|
+
|
|
411
|
+
- schedules are cron expressions
|
|
412
|
+
- only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
|
|
413
|
+
- Django admin exposes the same unschedule operation on recurring-task list and detail views
|
|
414
|
+
- multiple schedulers sharing the same recurring config dedupe firing in the database
|
|
415
|
+
- finished-job cleanup runs as internal scheduler maintenance when `preserve_finished_jobs=True` and `clear_finished_jobs_after` is set
|
|
416
|
+
- failed-job cleanup can run as internal scheduler maintenance when `clear_failed_jobs_after` is set
|
|
417
|
+
- recurring execution reservation cleanup can run as internal scheduler maintenance when `clear_recurring_executions_after` is set
|
|
418
|
+
|
|
350
419
|
## Concurrency Controls
|
|
351
420
|
|
|
352
421
|
Tasks can opt into database-backed concurrency limits.
|
|
@@ -392,15 +461,31 @@ orders.resume()
|
|
|
392
461
|
orders.clear()
|
|
393
462
|
```
|
|
394
463
|
|
|
464
|
+
Queue control notes:
|
|
465
|
+
|
|
466
|
+
- pausing a queue stops future claims, not enqueueing or already-claimed work
|
|
467
|
+
- `clear()` discards ready jobs only
|
|
468
|
+
- pass `backend_alias=` when you want to target a non-default `TASKS` alias
|
|
469
|
+
|
|
395
470
|
Operational commands:
|
|
396
471
|
|
|
397
472
|
```bash
|
|
398
473
|
python manage.py dj_queue_health
|
|
399
474
|
python manage.py dj_queue_health --max-age 120
|
|
400
475
|
python manage.py dj_queue_prune --older-than 86400
|
|
476
|
+
python manage.py dj_queue_prune --failed-older-than 604800
|
|
477
|
+
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
401
478
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
479
|
+
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
402
480
|
```
|
|
403
481
|
|
|
482
|
+
The runtime, health, and prune commands all accept `--backend` to target a
|
|
483
|
+
non-default backend alias.
|
|
484
|
+
|
|
485
|
+
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
486
|
+
task import path, while `--task-key` filters recurring execution cleanup by
|
|
487
|
+
recurring task key.
|
|
488
|
+
|
|
404
489
|
## Failed Jobs
|
|
405
490
|
|
|
406
491
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
@@ -417,6 +502,25 @@ retry_failed_job(job_id)
|
|
|
417
502
|
discard_failed_job(job_id)
|
|
418
503
|
```
|
|
419
504
|
|
|
505
|
+
Model helpers are available too:
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
from dj_queue.exceptions import UndiscardableError
|
|
509
|
+
from dj_queue.models import ClaimedExecution, FailedExecution
|
|
510
|
+
|
|
511
|
+
failed = FailedExecution.objects.get(job_id=job_id)
|
|
512
|
+
failed.retry()
|
|
513
|
+
failed.discard()
|
|
514
|
+
|
|
515
|
+
FailedExecution.retry_all(FailedExecution.objects.order_by("job_id"))
|
|
516
|
+
FailedExecution.discard_all_in_batches()
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
ClaimedExecution.discard_all_in_batches()
|
|
520
|
+
except UndiscardableError:
|
|
521
|
+
pass
|
|
522
|
+
```
|
|
523
|
+
|
|
420
524
|
Failures stay inspectable until you act on them.
|
|
421
525
|
|
|
422
526
|
## Multi-Database Setup
|
|
@@ -531,12 +635,19 @@ Start with these options:
|
|
|
531
635
|
- `dispatchers`: scheduled promotion and concurrency maintenance settings
|
|
532
636
|
- `scheduler`: dynamic recurring polling settings
|
|
533
637
|
- `database_alias`: database alias for queue tables and runtime activity
|
|
534
|
-
- `preserve_finished_jobs` and `clear_finished_jobs_after`: result retention and cleanup
|
|
638
|
+
- `preserve_finished_jobs` and `clear_finished_jobs_after`: successful result retention and cleanup
|
|
639
|
+
- `clear_failed_jobs_after`: optional failed-job retention window
|
|
640
|
+
- `clear_recurring_executions_after`: optional recurring reservation retention window
|
|
535
641
|
|
|
536
|
-
Additional operational tuning is available when needed
|
|
537
|
-
|
|
538
|
-
`
|
|
539
|
-
`
|
|
642
|
+
Additional operational tuning is available when needed:
|
|
643
|
+
|
|
644
|
+
- `use_skip_locked`: use `SKIP LOCKED` when the active backend supports it
|
|
645
|
+
- `listen_notify`: PostgreSQL-only worker wakeup optimization layered on top of polling
|
|
646
|
+
- `silence_polling`: suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger
|
|
647
|
+
- `process_heartbeat_interval` and `process_alive_threshold`: process liveness reporting and stale-runner detection
|
|
648
|
+
- `shutdown_timeout`: graceful drain window before standalone shutdown gives up on waiting
|
|
649
|
+
- `supervisor_pidfile`: optional pidfile guard for standalone supervisors
|
|
650
|
+
- `on_thread_error`: dotted callback path for runtime infrastructure exceptions
|
|
540
651
|
|
|
541
652
|
On PostgreSQL, `listen_notify` uses the same Django PostgreSQL driver
|
|
542
653
|
configuration as the main database connection. Install a compatible driver in
|
|
@@ -570,6 +681,8 @@ mode: async
|
|
|
570
681
|
database_alias: queue
|
|
571
682
|
preserve_finished_jobs: true
|
|
572
683
|
clear_finished_jobs_after: 86400
|
|
684
|
+
clear_failed_jobs_after: null
|
|
685
|
+
clear_recurring_executions_after: null
|
|
573
686
|
listen_notify: true
|
|
574
687
|
silence_polling: true
|
|
575
688
|
|
|
@@ -607,6 +720,63 @@ Environment overrides currently supported by `dj_queue` itself:
|
|
|
607
720
|
- `DJ_QUEUE_MODE`
|
|
608
721
|
- `DJ_QUEUE_SKIP_RECURRING`
|
|
609
722
|
|
|
723
|
+
## Lifecycle Hooks
|
|
724
|
+
|
|
725
|
+
Register hooks before starting the runtime, typically during Django startup.
|
|
726
|
+
Each callback receives the live supervisor or runner instance.
|
|
727
|
+
|
|
728
|
+
```python
|
|
729
|
+
from dj_queue.hooks import on_start, on_worker_start, register_hook
|
|
730
|
+
|
|
731
|
+
@on_start
|
|
732
|
+
def supervisor_started(process):
|
|
733
|
+
print(process.name)
|
|
734
|
+
|
|
735
|
+
@on_worker_start
|
|
736
|
+
def worker_started(process):
|
|
737
|
+
print(process.metadata)
|
|
738
|
+
|
|
739
|
+
@register_hook("scheduler.exit")
|
|
740
|
+
def scheduler_exited(process):
|
|
741
|
+
print(process.name)
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
Available hook helpers:
|
|
745
|
+
|
|
746
|
+
- supervisor: `on_start`, `on_stop`, `on_exit`
|
|
747
|
+
- worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
|
|
748
|
+
- dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
|
|
749
|
+
- scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
|
|
750
|
+
- generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
|
|
751
|
+
|
|
752
|
+
Hook notes:
|
|
753
|
+
|
|
754
|
+
- hooks fire in registration order
|
|
755
|
+
- hook failures do not block later hooks
|
|
756
|
+
- hook failures are isolated and routed through `on_thread_error`
|
|
757
|
+
|
|
758
|
+
### Runtime infrastructure errors
|
|
759
|
+
|
|
760
|
+
Set `on_thread_error` to a dotted callable path when you want custom handling
|
|
761
|
+
for queue-runtime exceptions:
|
|
762
|
+
|
|
763
|
+
```python
|
|
764
|
+
TASKS = {
|
|
765
|
+
"default": {
|
|
766
|
+
"BACKEND": "dj_queue.backend.DjQueueBackend",
|
|
767
|
+
"QUEUES": [],
|
|
768
|
+
"OPTIONS": {
|
|
769
|
+
"on_thread_error": "myapp.queue.report_runtime_error",
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
The callback receives the raised exception object for background runtime issues
|
|
776
|
+
such as hook failures, heartbeat failures, notify-watcher failures, and managed
|
|
777
|
+
runner crashes. It is not used for exceptions raised by your task code; those
|
|
778
|
+
become failed jobs instead.
|
|
779
|
+
|
|
610
780
|
## License
|
|
611
781
|
|
|
612
782
|
MIT
|