dj-queue 0.11.0__tar.gz → 0.13.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.11.0 → dj_queue-0.13.0}/PKG-INFO +18 -4
- {dj_queue-0.11.0 → dj_queue-0.13.0}/README.md +17 -3
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/admin.py +19 -6
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/api.py +2 -0
- dj_queue-0.13.0/dj_queue/apps.py +17 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/backend.py +29 -4
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/config.py +37 -11
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/dashboard.py +299 -71
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/db.py +10 -1
- dj_queue-0.13.0/dj_queue/management/commands/dj_queue_postgres_autovacuum.py +26 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/metrics.py +59 -0
- dj_queue-0.13.0/dj_queue/migrations/0012_job_djq_jobs_b_queue_id_idx_job_djq_jobs_b_conc_idx_and_more.py +33 -0
- dj_queue-0.13.0/dj_queue/migrations/0013_failedexecution_retry_at_and_more.py +21 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/models/jobs.py +7 -1
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/models/runtime.py +4 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/observability.py +367 -12
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/_helpers.py +31 -32
- dj_queue-0.13.0/dj_queue/operations/_insert.py +32 -0
- dj_queue-0.13.0/dj_queue/operations/concurrency.py +810 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/jobs.py +614 -337
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/queues.py +18 -21
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/recurring.py +90 -22
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/queue_state.py +125 -7
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/base.py +37 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/connection_budget.py +2 -7
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/dispatcher.py +11 -2
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/notify.py +10 -7
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/scheduler.py +34 -6
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/worker.py +16 -1
- dj_queue-0.13.0/dj_queue/sql/__init__.py +12 -0
- dj_queue-0.13.0/dj_queue/sql/common.py +220 -0
- dj_queue-0.13.0/dj_queue/sql/mysql.py +93 -0
- dj_queue-0.13.0/dj_queue/sql/postgres.py +526 -0
- dj_queue-0.13.0/dj_queue/sql/sqlite.py +8 -0
- dj_queue-0.13.0/dj_queue/sql/state.py +37 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/pyproject.toml +1 -1
- dj_queue-0.11.0/dj_queue/apps.py +0 -6
- dj_queue-0.11.0/dj_queue/operations/_insert.py +0 -43
- dj_queue-0.11.0/dj_queue/operations/concurrency.py +0 -865
- {dj_queue-0.11.0 → dj_queue-0.13.0}/LICENSE +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/contrib/prometheus.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/cron.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/dashboard_actions.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/log.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/0011_remove_blockedexecution_djq_bl_b_conc_idx_and_more.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/queue_selectors.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/runtime/topology.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/task_results.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/urls.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/views.py +0 -0
- {dj_queue-0.11.0 → dj_queue-0.13.0}/dj_queue/wakeup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -517,9 +517,10 @@ python manage.py dj_queue_prune --failed-older-than 604800
|
|
|
517
517
|
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
518
518
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
519
519
|
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
520
|
+
python manage.py dj_queue_postgres_autovacuum
|
|
520
521
|
```
|
|
521
522
|
|
|
522
|
-
The health and
|
|
523
|
+
The health, prune, and PostgreSQL autovacuum commands accept `--backend` to target a non-default backend alias.
|
|
523
524
|
|
|
524
525
|
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
525
526
|
task import path, while `--task-key` filters recurring execution cleanup by
|
|
@@ -543,9 +544,11 @@ from dj_queue.api import (
|
|
|
543
544
|
discard_scheduled_jobs,
|
|
544
545
|
retry_failed_job,
|
|
545
546
|
retry_failed_jobs,
|
|
547
|
+
schedule_failed_job_retry,
|
|
546
548
|
)
|
|
547
549
|
|
|
548
550
|
retry_failed_job(job_id)
|
|
551
|
+
schedule_failed_job_retry(job_id, retry_at=retry_at)
|
|
549
552
|
discard_failed_job(job_id)
|
|
550
553
|
retry_failed_jobs(job_ids=[job_id_a, job_id_b], batch_size=2)
|
|
551
554
|
discard_ready_jobs(job_ids=[ready_job_id], batch_size=1)
|
|
@@ -554,7 +557,9 @@ discard_scheduled_jobs(job_ids=[scheduled_job_id], batch_size=1)
|
|
|
554
557
|
discard_blocked_jobs(job_ids=[blocked_job_id], batch_size=1)
|
|
555
558
|
```
|
|
556
559
|
|
|
557
|
-
Failures stay inspectable until you act on them.
|
|
560
|
+
Failures stay inspectable until you act on them. Scheduled failed retries stay
|
|
561
|
+
failed until a dispatcher promotes rows whose `retry_at` is due; `dj_queue` does
|
|
562
|
+
not add an automatic retry policy or backoff engine.
|
|
558
563
|
|
|
559
564
|
## Errors When Enqueuing
|
|
560
565
|
|
|
@@ -714,6 +719,9 @@ policy, and autovacuum tuning.
|
|
|
714
719
|
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
715
720
|
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
716
721
|
conservative for queue workloads.
|
|
722
|
+
- Use `python manage.py dj_queue_postgres_autovacuum` to print reviewable
|
|
723
|
+
`ALTER TABLE ... SET (...)` SQL for queue-table autovacuum storage
|
|
724
|
+
parameters. `dj_queue` does not apply these settings in migrations.
|
|
717
725
|
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
718
726
|
transactions pin dead tuples and delay vacuum.
|
|
719
727
|
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
@@ -812,7 +820,7 @@ Global options:
|
|
|
812
820
|
| Option | Default | Meaning |
|
|
813
821
|
|---|---|---|
|
|
814
822
|
| `mode` | `"fork"` | standalone runtime mode, either `"fork"` or `"async"` |
|
|
815
|
-
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1}]` | worker topology and queue selectors |
|
|
823
|
+
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1, "prefetch_multiplier": 2}]` | worker topology and queue selectors |
|
|
816
824
|
| `dispatchers` | `[{"batch_size": 500, "polling_interval": 1, "concurrency_maintenance": true, "concurrency_maintenance_interval": 600}]` | scheduled promotion and concurrency maintenance |
|
|
817
825
|
| `scheduler` | `{"dynamic_tasks_enabled": false, "polling_interval": 5}` | dynamic recurring polling; the scheduler only starts when recurring or cleanup work exists |
|
|
818
826
|
| `recurring` | `{}` | static recurring task definitions keyed by name |
|
|
@@ -839,6 +847,7 @@ Worker entry options:
|
|
|
839
847
|
| `threads` | `3` | worker threads per worker process |
|
|
840
848
|
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
841
849
|
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
850
|
+
| `prefetch_multiplier` | `2` | upper bound on running plus queued claimed work; workers only poll when at least one thread is idle |
|
|
842
851
|
|
|
843
852
|
Dispatcher entry options:
|
|
844
853
|
|
|
@@ -914,6 +923,7 @@ queues = ["default", "email*"]
|
|
|
914
923
|
threads = 8
|
|
915
924
|
processes = 1
|
|
916
925
|
polling_interval = 0.1
|
|
926
|
+
prefetch_multiplier = 2
|
|
917
927
|
|
|
918
928
|
[[dispatchers]]
|
|
919
929
|
batch_size = 500
|
|
@@ -1018,6 +1028,10 @@ Exported metric families:
|
|
|
1018
1028
|
- `dj_queue_recurring_tasks{backend}`
|
|
1019
1029
|
- `dj_queue_semaphores{queue_database}`
|
|
1020
1030
|
- `dj_queue_process_rows{backend}`
|
|
1031
|
+
- `dj_queue_failed_jobs{backend}`
|
|
1032
|
+
- `dj_queue_failed_job_oldest_age_seconds{backend}`
|
|
1033
|
+
- `dj_queue_failed_job_retention_seconds{backend}`
|
|
1034
|
+
- `dj_queue_failed_jobs_over_retention{backend}`
|
|
1021
1035
|
|
|
1022
1036
|
Both endpoints support bearer token authentication. Set
|
|
1023
1037
|
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
@@ -491,9 +491,10 @@ python manage.py dj_queue_prune --failed-older-than 604800
|
|
|
491
491
|
python manage.py dj_queue_prune --recurring-older-than 2592000
|
|
492
492
|
python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
493
493
|
python manage.py dj_queue_prune --task-key nightly_cleanup
|
|
494
|
+
python manage.py dj_queue_postgres_autovacuum
|
|
494
495
|
```
|
|
495
496
|
|
|
496
|
-
The health and
|
|
497
|
+
The health, prune, and PostgreSQL autovacuum commands accept `--backend` to target a non-default backend alias.
|
|
497
498
|
|
|
498
499
|
For `dj_queue_prune`, `--task-path` filters finished and failed job cleanup by
|
|
499
500
|
task import path, while `--task-key` filters recurring execution cleanup by
|
|
@@ -517,9 +518,11 @@ from dj_queue.api import (
|
|
|
517
518
|
discard_scheduled_jobs,
|
|
518
519
|
retry_failed_job,
|
|
519
520
|
retry_failed_jobs,
|
|
521
|
+
schedule_failed_job_retry,
|
|
520
522
|
)
|
|
521
523
|
|
|
522
524
|
retry_failed_job(job_id)
|
|
525
|
+
schedule_failed_job_retry(job_id, retry_at=retry_at)
|
|
523
526
|
discard_failed_job(job_id)
|
|
524
527
|
retry_failed_jobs(job_ids=[job_id_a, job_id_b], batch_size=2)
|
|
525
528
|
discard_ready_jobs(job_ids=[ready_job_id], batch_size=1)
|
|
@@ -528,7 +531,9 @@ discard_scheduled_jobs(job_ids=[scheduled_job_id], batch_size=1)
|
|
|
528
531
|
discard_blocked_jobs(job_ids=[blocked_job_id], batch_size=1)
|
|
529
532
|
```
|
|
530
533
|
|
|
531
|
-
Failures stay inspectable until you act on them.
|
|
534
|
+
Failures stay inspectable until you act on them. Scheduled failed retries stay
|
|
535
|
+
failed until a dispatcher promotes rows whose `retry_at` is due; `dj_queue` does
|
|
536
|
+
not add an automatic retry policy or backoff engine.
|
|
532
537
|
|
|
533
538
|
## Errors When Enqueuing
|
|
534
539
|
|
|
@@ -688,6 +693,9 @@ policy, and autovacuum tuning.
|
|
|
688
693
|
- Tune autovacuum for `dj_queue_jobs` and the high-churn
|
|
689
694
|
`dj_queue_*_executions` tables, often default OLTP settings are too
|
|
690
695
|
conservative for queue workloads.
|
|
696
|
+
- Use `python manage.py dj_queue_postgres_autovacuum` to print reviewable
|
|
697
|
+
`ALTER TABLE ... SET (...)` SQL for queue-table autovacuum storage
|
|
698
|
+
parameters. `dj_queue` does not apply these settings in migrations.
|
|
691
699
|
- Keep transactions short across workers and the rest of your app. Long-lived
|
|
692
700
|
transactions pin dead tuples and delay vacuum.
|
|
693
701
|
- Monitor dead tuples, autovacuum frequency, and long-running queries before
|
|
@@ -786,7 +794,7 @@ Global options:
|
|
|
786
794
|
| Option | Default | Meaning |
|
|
787
795
|
|---|---|---|
|
|
788
796
|
| `mode` | `"fork"` | standalone runtime mode, either `"fork"` or `"async"` |
|
|
789
|
-
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1}]` | worker topology and queue selectors |
|
|
797
|
+
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1, "prefetch_multiplier": 2}]` | worker topology and queue selectors |
|
|
790
798
|
| `dispatchers` | `[{"batch_size": 500, "polling_interval": 1, "concurrency_maintenance": true, "concurrency_maintenance_interval": 600}]` | scheduled promotion and concurrency maintenance |
|
|
791
799
|
| `scheduler` | `{"dynamic_tasks_enabled": false, "polling_interval": 5}` | dynamic recurring polling; the scheduler only starts when recurring or cleanup work exists |
|
|
792
800
|
| `recurring` | `{}` | static recurring task definitions keyed by name |
|
|
@@ -813,6 +821,7 @@ Worker entry options:
|
|
|
813
821
|
| `threads` | `3` | worker threads per worker process |
|
|
814
822
|
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
815
823
|
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
824
|
+
| `prefetch_multiplier` | `2` | upper bound on running plus queued claimed work; workers only poll when at least one thread is idle |
|
|
816
825
|
|
|
817
826
|
Dispatcher entry options:
|
|
818
827
|
|
|
@@ -888,6 +897,7 @@ queues = ["default", "email*"]
|
|
|
888
897
|
threads = 8
|
|
889
898
|
processes = 1
|
|
890
899
|
polling_interval = 0.1
|
|
900
|
+
prefetch_multiplier = 2
|
|
891
901
|
|
|
892
902
|
[[dispatchers]]
|
|
893
903
|
batch_size = 500
|
|
@@ -992,6 +1002,10 @@ Exported metric families:
|
|
|
992
1002
|
- `dj_queue_recurring_tasks{backend}`
|
|
993
1003
|
- `dj_queue_semaphores{queue_database}`
|
|
994
1004
|
- `dj_queue_process_rows{backend}`
|
|
1005
|
+
- `dj_queue_failed_jobs{backend}`
|
|
1006
|
+
- `dj_queue_failed_job_oldest_age_seconds{backend}`
|
|
1007
|
+
- `dj_queue_failed_job_retention_seconds{backend}`
|
|
1008
|
+
- `dj_queue_failed_jobs_over_retention{backend}`
|
|
995
1009
|
|
|
996
1010
|
Both endpoints support bearer token authentication. Set
|
|
997
1011
|
`DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
|
|
@@ -82,9 +82,6 @@ def _install_dj_queue_admin_site(site):
|
|
|
82
82
|
site._dj_queue_dashboard_installed = True
|
|
83
83
|
|
|
84
84
|
|
|
85
|
-
_install_dj_queue_admin_site(admin.site)
|
|
86
|
-
|
|
87
|
-
|
|
88
85
|
def _format_admin_datetime(value):
|
|
89
86
|
if value is None:
|
|
90
87
|
return None
|
|
@@ -530,7 +527,19 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
530
527
|
|
|
531
528
|
def get_queryset(self, request):
|
|
532
529
|
queryset = super().get_queryset(request)
|
|
533
|
-
|
|
530
|
+
if self._orders_by_status(request):
|
|
531
|
+
return queryset.annotate(status_rank=status_rank_expression())
|
|
532
|
+
return queryset
|
|
533
|
+
|
|
534
|
+
def _orders_by_status(self, request):
|
|
535
|
+
ordering = request.GET.get("o")
|
|
536
|
+
if not ordering:
|
|
537
|
+
return False
|
|
538
|
+
try:
|
|
539
|
+
status_index = self.get_list_display(request).index("display_status") + 1
|
|
540
|
+
except ValueError:
|
|
541
|
+
return False
|
|
542
|
+
return any(part.lstrip("-") == str(status_index) for part in ordering.split("."))
|
|
534
543
|
|
|
535
544
|
@admin.display(description="status", ordering="status_rank")
|
|
536
545
|
def display_status(self, obj):
|
|
@@ -707,14 +716,14 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
707
716
|
class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
708
717
|
backend_filter_field = "job__backend_alias"
|
|
709
718
|
ignored_filter_params = ("job__backend_alias",)
|
|
710
|
-
list_display = ("job", "exception_class", "message", "display_created_at")
|
|
719
|
+
list_display = ("job", "exception_class", "message", "display_retry_at", "display_created_at")
|
|
711
720
|
list_filter = (
|
|
712
721
|
("job__queue_name", admin.AllValuesFieldListFilter),
|
|
713
722
|
"exception_class",
|
|
714
723
|
)
|
|
715
724
|
list_select_related = ("job",)
|
|
716
725
|
actions = ("retry_jobs", "discard_jobs")
|
|
717
|
-
readonly_fields = ("job", "exception_class", "message", "traceback", "created_at")
|
|
726
|
+
readonly_fields = ("job", "exception_class", "message", "traceback", "retry_at", "created_at")
|
|
718
727
|
search_fields = ("job__id", "job__task_path", "message", "exception_class")
|
|
719
728
|
|
|
720
729
|
@admin.action(description="Retry selected failed jobs")
|
|
@@ -747,6 +756,10 @@ class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
747
756
|
def display_created_at(self, obj):
|
|
748
757
|
return _format_admin_datetime(obj.created_at)
|
|
749
758
|
|
|
759
|
+
@admin.display(description="retry at", ordering="retry_at")
|
|
760
|
+
def display_retry_at(self, obj):
|
|
761
|
+
return _format_admin_datetime(obj.retry_at)
|
|
762
|
+
|
|
750
763
|
def get_change_actions(self, request, obj):
|
|
751
764
|
if obj is None:
|
|
752
765
|
return ()
|
|
@@ -15,6 +15,7 @@ from dj_queue.operations.jobs import (
|
|
|
15
15
|
execute_claimed_job,
|
|
16
16
|
retry_failed_job,
|
|
17
17
|
retry_failed_jobs,
|
|
18
|
+
schedule_failed_job_retry,
|
|
18
19
|
)
|
|
19
20
|
from dj_queue.operations.queues import pause_queue, resume_queue
|
|
20
21
|
from dj_queue.operations.recurring import schedule_recurring_task, unschedule_recurring_task
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"execute_claimed_job",
|
|
33
34
|
"retry_failed_job",
|
|
34
35
|
"retry_failed_jobs",
|
|
36
|
+
"schedule_failed_job_retry",
|
|
35
37
|
"schedule_recurring_task",
|
|
36
38
|
"unschedule_recurring_task",
|
|
37
39
|
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
from django.apps import apps
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DjQueueConfig(AppConfig):
|
|
6
|
+
name = "dj_queue"
|
|
7
|
+
verbose_name = "dj_queue"
|
|
8
|
+
|
|
9
|
+
def ready(self):
|
|
10
|
+
if not apps.is_installed("django.contrib.admin"):
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
from django.contrib import admin
|
|
14
|
+
|
|
15
|
+
from dj_queue.admin import _install_dj_queue_admin_site
|
|
16
|
+
|
|
17
|
+
_install_dj_queue_admin_site(admin.site)
|
|
@@ -3,6 +3,7 @@ from django.db import close_old_connections, connections
|
|
|
3
3
|
from django.tasks.backends.base import BaseTaskBackend
|
|
4
4
|
from django.tasks.exceptions import TaskResultDoesNotExist
|
|
5
5
|
|
|
6
|
+
from dj_queue.config import load_backend_config
|
|
6
7
|
from dj_queue.db import get_database_alias
|
|
7
8
|
from dj_queue.models import Job
|
|
8
9
|
from dj_queue.operations.jobs import (
|
|
@@ -47,8 +48,13 @@ class DjQueueBackend(BaseTaskBackend):
|
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
async def aenqueue(self, task, args, kwargs):
|
|
50
|
-
|
|
51
|
+
config = load_backend_config(self.alias)
|
|
52
|
+
return await sync_to_async(
|
|
53
|
+
_async_backend_call,
|
|
54
|
+
thread_sensitive=config.async_thread_sensitive,
|
|
55
|
+
)(
|
|
51
56
|
self.enqueue,
|
|
57
|
+
close_connections=config.async_close_connections,
|
|
52
58
|
task=task,
|
|
53
59
|
args=args,
|
|
54
60
|
kwargs=kwargs,
|
|
@@ -70,6 +76,17 @@ class DjQueueBackend(BaseTaskBackend):
|
|
|
70
76
|
for job, task, dispatch_outcome in created_jobs
|
|
71
77
|
]
|
|
72
78
|
|
|
79
|
+
async def aenqueue_all(self, task_calls):
|
|
80
|
+
config = load_backend_config(self.alias)
|
|
81
|
+
return await sync_to_async(
|
|
82
|
+
_async_backend_call,
|
|
83
|
+
thread_sensitive=config.async_thread_sensitive,
|
|
84
|
+
)(
|
|
85
|
+
self.enqueue_all,
|
|
86
|
+
close_connections=config.async_close_connections,
|
|
87
|
+
task_calls=task_calls,
|
|
88
|
+
)
|
|
89
|
+
|
|
73
90
|
def get_result(self, result_id):
|
|
74
91
|
alias = get_database_alias(self.alias)
|
|
75
92
|
try:
|
|
@@ -90,15 +107,23 @@ class DjQueueBackend(BaseTaskBackend):
|
|
|
90
107
|
return task_result_from_job(job)
|
|
91
108
|
|
|
92
109
|
async def aget_result(self, result_id):
|
|
93
|
-
|
|
110
|
+
config = load_backend_config(self.alias)
|
|
111
|
+
return await sync_to_async(
|
|
112
|
+
_async_backend_call,
|
|
113
|
+
thread_sensitive=config.async_thread_sensitive,
|
|
114
|
+
)(
|
|
94
115
|
self.get_result,
|
|
116
|
+
close_connections=config.async_close_connections,
|
|
95
117
|
result_id=result_id,
|
|
96
118
|
)
|
|
97
119
|
|
|
98
120
|
|
|
99
|
-
def _async_backend_call(method, /, **kwargs):
|
|
121
|
+
def _async_backend_call(method, /, *, close_connections=False, **kwargs):
|
|
100
122
|
close_old_connections()
|
|
101
123
|
try:
|
|
102
124
|
return method(**kwargs)
|
|
103
125
|
finally:
|
|
104
|
-
|
|
126
|
+
if close_connections:
|
|
127
|
+
connections.close_all()
|
|
128
|
+
else:
|
|
129
|
+
close_old_connections()
|
|
@@ -19,6 +19,7 @@ DEFAULT_WORKER = {
|
|
|
19
19
|
"threads": 3,
|
|
20
20
|
"processes": 1,
|
|
21
21
|
"polling_interval": 0.1,
|
|
22
|
+
"prefetch_multiplier": 2,
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
DEFAULT_DISPATCHER = {
|
|
@@ -52,6 +53,8 @@ DEFAULT_OPTIONS = {
|
|
|
52
53
|
"use_skip_locked": True,
|
|
53
54
|
"listen_notify": True,
|
|
54
55
|
"silence_polling": True,
|
|
56
|
+
"async_thread_sensitive": False,
|
|
57
|
+
"async_close_connections": False,
|
|
55
58
|
"on_thread_error": None,
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -75,6 +78,7 @@ class WorkerConfig(ConfigValue):
|
|
|
75
78
|
threads: int = 3
|
|
76
79
|
processes: int = 1
|
|
77
80
|
polling_interval: float = 0.1
|
|
81
|
+
prefetch_multiplier: int = 2
|
|
78
82
|
|
|
79
83
|
|
|
80
84
|
@dataclass(frozen=True, slots=True)
|
|
@@ -125,6 +129,8 @@ class BackendConfig(ConfigValue):
|
|
|
125
129
|
use_skip_locked: bool = True
|
|
126
130
|
listen_notify: bool = True
|
|
127
131
|
silence_polling: bool = True
|
|
132
|
+
async_thread_sensitive: bool = False
|
|
133
|
+
async_close_connections: bool = False
|
|
128
134
|
on_thread_error: str | None = None
|
|
129
135
|
skip_recurring: bool = False
|
|
130
136
|
only_work: bool = False
|
|
@@ -151,21 +157,26 @@ def load_backend_config(
|
|
|
151
157
|
|
|
152
158
|
ensure_dj_queue_backend_alias(tasks_settings, backend_alias)
|
|
153
159
|
backend_block = _backend_block(tasks_settings, backend_alias)
|
|
154
|
-
|
|
160
|
+
env_key = tuple(env.get(key) for key in CONFIG_ENV_KEYS)
|
|
155
161
|
cache_key = (
|
|
156
162
|
backend_alias,
|
|
157
|
-
_cache_key(cli_overrides),
|
|
158
|
-
|
|
163
|
+
_EMPTY_CLI_CACHE_KEY if not cli_overrides else _cache_key(cli_overrides),
|
|
164
|
+
env_key,
|
|
159
165
|
_cache_key(backend_block),
|
|
160
166
|
)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
cached = _BACKEND_CONFIG_CACHE.get(cache_key)
|
|
168
|
+
if cached is not None:
|
|
169
|
+
return cached
|
|
170
|
+
|
|
171
|
+
env_values = {key: value for key, value in zip(CONFIG_ENV_KEYS, env_key) if value is not None}
|
|
172
|
+
config = _load_backend_config_uncached(
|
|
173
|
+
backend_alias,
|
|
174
|
+
cli_overrides,
|
|
175
|
+
env_values,
|
|
176
|
+
tasks_settings,
|
|
177
|
+
)
|
|
178
|
+
_BACKEND_CONFIG_CACHE[cache_key] = config
|
|
179
|
+
return config
|
|
169
180
|
|
|
170
181
|
|
|
171
182
|
def load_allowed_queues(
|
|
@@ -272,6 +283,12 @@ def _load_backend_config_uncached(
|
|
|
272
283
|
use_skip_locked=_bool_option(resolved_options["use_skip_locked"], "use_skip_locked"),
|
|
273
284
|
listen_notify=_bool_option(resolved_options["listen_notify"], "listen_notify"),
|
|
274
285
|
silence_polling=_bool_option(resolved_options["silence_polling"], "silence_polling"),
|
|
286
|
+
async_thread_sensitive=_bool_option(
|
|
287
|
+
resolved_options["async_thread_sensitive"], "async_thread_sensitive"
|
|
288
|
+
),
|
|
289
|
+
async_close_connections=_bool_option(
|
|
290
|
+
resolved_options["async_close_connections"], "async_close_connections"
|
|
291
|
+
),
|
|
275
292
|
on_thread_error=on_thread_error,
|
|
276
293
|
skip_recurring=skip_recurring,
|
|
277
294
|
only_work=only_work,
|
|
@@ -370,6 +387,8 @@ def _load_toml_options(config_path: Any, *, backend_alias: str) -> dict[str, Any
|
|
|
370
387
|
|
|
371
388
|
try:
|
|
372
389
|
config_payload = tomllib.loads(Path(config_path).read_text(encoding="utf-8"))
|
|
390
|
+
except OSError as exc:
|
|
391
|
+
raise ImproperlyConfigured(f"DJ_QUEUE_CONFIG could not be read: {exc}") from exc
|
|
373
392
|
except tomllib.TOMLDecodeError as exc:
|
|
374
393
|
raise ImproperlyConfigured(f"DJ_QUEUE_CONFIG TOML is invalid: {exc}") from exc
|
|
375
394
|
if not isinstance(config_payload, dict):
|
|
@@ -475,6 +494,10 @@ def _build_worker_configs(raw_workers: Any, mode: str) -> tuple[WorkerConfig, ..
|
|
|
475
494
|
raw_worker.get("polling_interval", DEFAULT_WORKER["polling_interval"]),
|
|
476
495
|
f"workers[{index}].polling_interval",
|
|
477
496
|
),
|
|
497
|
+
prefetch_multiplier=_positive_int(
|
|
498
|
+
raw_worker.get("prefetch_multiplier", DEFAULT_WORKER["prefetch_multiplier"]),
|
|
499
|
+
f"workers[{index}].prefetch_multiplier",
|
|
500
|
+
),
|
|
478
501
|
)
|
|
479
502
|
|
|
480
503
|
if mode == "async" and worker.processes > 1:
|
|
@@ -758,3 +781,6 @@ def _cache_key(value: Any) -> str:
|
|
|
758
781
|
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
|
759
782
|
except (TypeError, ValueError) as exc:
|
|
760
783
|
raise ImproperlyConfigured("dj_queue config values must be JSON-serializable") from exc
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
_EMPTY_CLI_CACHE_KEY = _cache_key({})
|