dj-queue 0.9.1__tar.gz → 0.10.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.9.1 → dj_queue-0.10.0}/PKG-INFO +69 -22
- {dj_queue-0.9.1 → dj_queue-0.10.0}/README.md +68 -21
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/admin.py +18 -24
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/api.py +15 -12
- dj_queue-0.10.0/dj_queue/backend.py +93 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/config.py +94 -28
- dj_queue-0.10.0/dj_queue/contrib/asgi.py +148 -0
- dj_queue-0.10.0/dj_queue/contrib/gunicorn.py +154 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/dashboard.py +63 -13
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/db.py +6 -2
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/log.py +13 -2
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/metrics.py +4 -3
- dj_queue-0.10.0/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +36 -0
- dj_queue-0.10.0/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +23 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/models/recurring.py +0 -12
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/models/runtime.py +13 -2
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/observability.py +15 -23
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/_helpers.py +89 -1
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/cleanup.py +24 -12
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/concurrency.py +132 -34
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/jobs.py +359 -192
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/recurring.py +100 -29
- dj_queue-0.10.0/dj_queue/queue_selectors.py +62 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/queue_state.py +26 -6
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/base.py +52 -20
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/connection_budget.py +3 -2
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/dispatcher.py +2 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/notify.py +5 -18
- dj_queue-0.10.0/dj_queue/runtime/pidfile.py +68 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/scheduler.py +2 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/supervisor.py +56 -18
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/worker.py +4 -1
- dj_queue-0.10.0/dj_queue/task_results.py +128 -0
- dj_queue-0.10.0/dj_queue/wakeup.py +24 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/pyproject.toml +2 -1
- dj_queue-0.9.1/dj_queue/backend.py +0 -170
- dj_queue-0.9.1/dj_queue/contrib/asgi.py +0 -47
- dj_queue-0.9.1/dj_queue/contrib/gunicorn.py +0 -45
- dj_queue-0.9.1/dj_queue/runtime/pidfile.py +0 -39
- {dj_queue-0.9.1 → dj_queue-0.10.0}/LICENSE +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/apps.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/contrib/prometheus.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/cron.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/hooks.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/_insert.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/operations/queues.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/routers.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/runtime/topology.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/urls.py +0 -0
- {dj_queue-0.9.1 → dj_queue-0.10.0}/dj_queue/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -713,9 +713,12 @@ from django.core.asgi import get_asgi_application
|
|
|
713
713
|
from dj_queue.contrib.asgi import DjQueueLifespan
|
|
714
714
|
|
|
715
715
|
django_application = get_asgi_application()
|
|
716
|
-
application = DjQueueLifespan(django_application)
|
|
716
|
+
application = DjQueueLifespan(django_application, forward_wrapped_lifespan=False)
|
|
717
717
|
```
|
|
718
718
|
|
|
719
|
+
Set `forward_wrapped_lifespan=False` when the wrapped app does not implement the
|
|
720
|
+
ASGI lifespan protocol itself, such as Django's plain `get_asgi_application()`.
|
|
721
|
+
|
|
719
722
|
### Gunicorn
|
|
720
723
|
|
|
721
724
|
Import the provided hooks in your Gunicorn config:
|
|
@@ -780,26 +783,70 @@ concurrency-maintenance throughput.
|
|
|
780
783
|
|
|
781
784
|
The main configuration lives in `TASKS[backend_alias]["OPTIONS"]`.
|
|
782
785
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
786
|
+
No top-level `OPTIONS` key is required. Omit a key to use its default. Static
|
|
787
|
+
`recurring` entries are the exception: each named recurring task requires
|
|
788
|
+
`task_path` and `schedule`.
|
|
789
|
+
|
|
790
|
+
Global options:
|
|
791
|
+
|
|
792
|
+
| Option | Default | Meaning |
|
|
793
|
+
|---|---|---|
|
|
794
|
+
| `mode` | `"fork"` | standalone runtime mode, either `"fork"` or `"async"` |
|
|
795
|
+
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1}]` | worker topology and queue selectors |
|
|
796
|
+
| `dispatchers` | `[{"batch_size": 500, "polling_interval": 1, "concurrency_maintenance": true, "concurrency_maintenance_interval": 600}]` | scheduled promotion and concurrency maintenance |
|
|
797
|
+
| `scheduler` | `{"dynamic_tasks_enabled": false, "polling_interval": 5}` | dynamic recurring polling; the scheduler only starts when recurring or cleanup work exists |
|
|
798
|
+
| `recurring` | `{}` | static recurring task definitions keyed by name |
|
|
799
|
+
| `database_alias` | `"default"` | database alias for queue tables and runtime activity |
|
|
800
|
+
| `preserve_finished_jobs` | `true` | keep successful jobs for result lookup and retention cleanup |
|
|
801
|
+
| `clear_finished_jobs_after` | `86400` | seconds before finished successful jobs are cleaned up |
|
|
802
|
+
| `clear_failed_jobs_after` | `null` | optional failed-job retention window in seconds |
|
|
803
|
+
| `clear_recurring_executions_after` | `null` | optional recurring reservation retention window in seconds |
|
|
804
|
+
| `default_concurrency_duration` | `180` | default semaphore TTL in seconds |
|
|
805
|
+
| `use_skip_locked` | `true` | use `SKIP LOCKED` when the active backend supports it |
|
|
806
|
+
| `listen_notify` | `true` | PostgreSQL-only worker wakeup optimization layered on top of polling |
|
|
807
|
+
| `silence_polling` | `true` | suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger |
|
|
808
|
+
| `process_heartbeat_interval` | `60` | seconds between process heartbeat writes |
|
|
809
|
+
| `process_alive_threshold` | `300` | seconds before a process row is stale |
|
|
810
|
+
| `shutdown_timeout` | `5` | graceful drain window before standalone shutdown gives up on waiting |
|
|
811
|
+
| `supervisor_pidfile` | `null` | optional pidfile guard for standalone supervisors |
|
|
812
|
+
| `on_thread_error` | `null` | dotted callback path for runtime infrastructure exceptions |
|
|
813
|
+
|
|
814
|
+
Worker entry options:
|
|
815
|
+
|
|
816
|
+
| Option | Default | Meaning |
|
|
817
|
+
|---|---|---|
|
|
818
|
+
| `queues` | `"*"` | queue selector or selectors; `"*"` means all queues |
|
|
819
|
+
| `threads` | `3` | worker threads per worker process |
|
|
820
|
+
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
821
|
+
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
822
|
+
|
|
823
|
+
Dispatcher entry options:
|
|
824
|
+
|
|
825
|
+
| Option | Default | Meaning |
|
|
826
|
+
|---|---|---|
|
|
827
|
+
| `batch_size` | `500` | maximum scheduled or blocked rows to promote per batch |
|
|
828
|
+
| `polling_interval` | `1` | seconds between dispatcher polls |
|
|
829
|
+
| `concurrency_maintenance` | `true` | run expired semaphore and blocked-job maintenance |
|
|
830
|
+
| `concurrency_maintenance_interval` | `600` | seconds between maintenance passes; `0` means every poll |
|
|
831
|
+
|
|
832
|
+
Scheduler entry options:
|
|
833
|
+
|
|
834
|
+
| Option | Default | Meaning |
|
|
835
|
+
|---|---|---|
|
|
836
|
+
| `dynamic_tasks_enabled` | `false` | load dynamic recurring tasks from the database |
|
|
837
|
+
| `polling_interval` | `5` | seconds between scheduler polls |
|
|
838
|
+
|
|
839
|
+
Recurring entry options:
|
|
840
|
+
|
|
841
|
+
| Option | Default | Meaning |
|
|
842
|
+
|---|---|---|
|
|
843
|
+
| `task_path` | none | required dotted import path for the task to enqueue |
|
|
844
|
+
| `schedule` | none | required cron or supported Fugit-style cronish schedule |
|
|
845
|
+
| `args` | `[]` | positional arguments for the task |
|
|
846
|
+
| `kwargs` | `{}` | keyword arguments for the task |
|
|
847
|
+
| `queue_name` | `"default"` | queue used for jobs created from this recurring task |
|
|
848
|
+
| `priority` | `0` | priority used for jobs created from this recurring task |
|
|
849
|
+
| `description` | `""` | operator-facing description |
|
|
803
850
|
|
|
804
851
|
On PostgreSQL, `listen_notify` uses the same Django PostgreSQL driver
|
|
805
852
|
configuration as the main database connection. Install a compatible driver in
|
|
@@ -687,9 +687,12 @@ from django.core.asgi import get_asgi_application
|
|
|
687
687
|
from dj_queue.contrib.asgi import DjQueueLifespan
|
|
688
688
|
|
|
689
689
|
django_application = get_asgi_application()
|
|
690
|
-
application = DjQueueLifespan(django_application)
|
|
690
|
+
application = DjQueueLifespan(django_application, forward_wrapped_lifespan=False)
|
|
691
691
|
```
|
|
692
692
|
|
|
693
|
+
Set `forward_wrapped_lifespan=False` when the wrapped app does not implement the
|
|
694
|
+
ASGI lifespan protocol itself, such as Django's plain `get_asgi_application()`.
|
|
695
|
+
|
|
693
696
|
### Gunicorn
|
|
694
697
|
|
|
695
698
|
Import the provided hooks in your Gunicorn config:
|
|
@@ -754,26 +757,70 @@ concurrency-maintenance throughput.
|
|
|
754
757
|
|
|
755
758
|
The main configuration lives in `TASKS[backend_alias]["OPTIONS"]`.
|
|
756
759
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
760
|
+
No top-level `OPTIONS` key is required. Omit a key to use its default. Static
|
|
761
|
+
`recurring` entries are the exception: each named recurring task requires
|
|
762
|
+
`task_path` and `schedule`.
|
|
763
|
+
|
|
764
|
+
Global options:
|
|
765
|
+
|
|
766
|
+
| Option | Default | Meaning |
|
|
767
|
+
|---|---|---|
|
|
768
|
+
| `mode` | `"fork"` | standalone runtime mode, either `"fork"` or `"async"` |
|
|
769
|
+
| `workers` | `[{"queues": "*", "threads": 3, "processes": 1, "polling_interval": 0.1}]` | worker topology and queue selectors |
|
|
770
|
+
| `dispatchers` | `[{"batch_size": 500, "polling_interval": 1, "concurrency_maintenance": true, "concurrency_maintenance_interval": 600}]` | scheduled promotion and concurrency maintenance |
|
|
771
|
+
| `scheduler` | `{"dynamic_tasks_enabled": false, "polling_interval": 5}` | dynamic recurring polling; the scheduler only starts when recurring or cleanup work exists |
|
|
772
|
+
| `recurring` | `{}` | static recurring task definitions keyed by name |
|
|
773
|
+
| `database_alias` | `"default"` | database alias for queue tables and runtime activity |
|
|
774
|
+
| `preserve_finished_jobs` | `true` | keep successful jobs for result lookup and retention cleanup |
|
|
775
|
+
| `clear_finished_jobs_after` | `86400` | seconds before finished successful jobs are cleaned up |
|
|
776
|
+
| `clear_failed_jobs_after` | `null` | optional failed-job retention window in seconds |
|
|
777
|
+
| `clear_recurring_executions_after` | `null` | optional recurring reservation retention window in seconds |
|
|
778
|
+
| `default_concurrency_duration` | `180` | default semaphore TTL in seconds |
|
|
779
|
+
| `use_skip_locked` | `true` | use `SKIP LOCKED` when the active backend supports it |
|
|
780
|
+
| `listen_notify` | `true` | PostgreSQL-only worker wakeup optimization layered on top of polling |
|
|
781
|
+
| `silence_polling` | `true` | suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger |
|
|
782
|
+
| `process_heartbeat_interval` | `60` | seconds between process heartbeat writes |
|
|
783
|
+
| `process_alive_threshold` | `300` | seconds before a process row is stale |
|
|
784
|
+
| `shutdown_timeout` | `5` | graceful drain window before standalone shutdown gives up on waiting |
|
|
785
|
+
| `supervisor_pidfile` | `null` | optional pidfile guard for standalone supervisors |
|
|
786
|
+
| `on_thread_error` | `null` | dotted callback path for runtime infrastructure exceptions |
|
|
787
|
+
|
|
788
|
+
Worker entry options:
|
|
789
|
+
|
|
790
|
+
| Option | Default | Meaning |
|
|
791
|
+
|---|---|---|
|
|
792
|
+
| `queues` | `"*"` | queue selector or selectors; `"*"` means all queues |
|
|
793
|
+
| `threads` | `3` | worker threads per worker process |
|
|
794
|
+
| `processes` | `1` | worker processes in `fork` mode; normalized to `1` in `async` mode |
|
|
795
|
+
| `polling_interval` | `0.1` | seconds between worker polls |
|
|
796
|
+
|
|
797
|
+
Dispatcher entry options:
|
|
798
|
+
|
|
799
|
+
| Option | Default | Meaning |
|
|
800
|
+
|---|---|---|
|
|
801
|
+
| `batch_size` | `500` | maximum scheduled or blocked rows to promote per batch |
|
|
802
|
+
| `polling_interval` | `1` | seconds between dispatcher polls |
|
|
803
|
+
| `concurrency_maintenance` | `true` | run expired semaphore and blocked-job maintenance |
|
|
804
|
+
| `concurrency_maintenance_interval` | `600` | seconds between maintenance passes; `0` means every poll |
|
|
805
|
+
|
|
806
|
+
Scheduler entry options:
|
|
807
|
+
|
|
808
|
+
| Option | Default | Meaning |
|
|
809
|
+
|---|---|---|
|
|
810
|
+
| `dynamic_tasks_enabled` | `false` | load dynamic recurring tasks from the database |
|
|
811
|
+
| `polling_interval` | `5` | seconds between scheduler polls |
|
|
812
|
+
|
|
813
|
+
Recurring entry options:
|
|
814
|
+
|
|
815
|
+
| Option | Default | Meaning |
|
|
816
|
+
|---|---|---|
|
|
817
|
+
| `task_path` | none | required dotted import path for the task to enqueue |
|
|
818
|
+
| `schedule` | none | required cron or supported Fugit-style cronish schedule |
|
|
819
|
+
| `args` | `[]` | positional arguments for the task |
|
|
820
|
+
| `kwargs` | `{}` | keyword arguments for the task |
|
|
821
|
+
| `queue_name` | `"default"` | queue used for jobs created from this recurring task |
|
|
822
|
+
| `priority` | `0` | priority used for jobs created from this recurring task |
|
|
823
|
+
| `description` | `""` | operator-facing description |
|
|
777
824
|
|
|
778
825
|
On PostgreSQL, `listen_notify` uses the same Django PostgreSQL driver
|
|
779
826
|
configuration as the main database connection. Install a compatible driver in
|
|
@@ -29,13 +29,19 @@ from dj_queue.models import (
|
|
|
29
29
|
Semaphore,
|
|
30
30
|
)
|
|
31
31
|
from dj_queue.operations.jobs import (
|
|
32
|
+
DispatchOutcome,
|
|
32
33
|
discard_failed_job,
|
|
33
34
|
dispatch_scheduled_job_now,
|
|
34
35
|
enqueue_job_again,
|
|
35
36
|
retry_failed_job,
|
|
36
37
|
retry_failed_jobs,
|
|
37
38
|
)
|
|
38
|
-
from dj_queue.queue_state import
|
|
39
|
+
from dj_queue.queue_state import (
|
|
40
|
+
QUEUE_STATES,
|
|
41
|
+
filter_queue_state,
|
|
42
|
+
is_queue_state,
|
|
43
|
+
status_rank_expression,
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
class DjQueueFirstAdminSite(admin.AdminSite):
|
|
@@ -328,29 +334,12 @@ class JobStatusListFilter(admin.SimpleListFilter):
|
|
|
328
334
|
parameter_name = "status"
|
|
329
335
|
|
|
330
336
|
def lookups(self, request, model_admin):
|
|
331
|
-
return
|
|
332
|
-
("ready", "ready"),
|
|
333
|
-
("scheduled", "scheduled"),
|
|
334
|
-
("claimed", "claimed"),
|
|
335
|
-
("blocked", "blocked"),
|
|
336
|
-
("failed", "failed"),
|
|
337
|
-
("finished", "finished"),
|
|
338
|
-
)
|
|
337
|
+
return QUEUE_STATES
|
|
339
338
|
|
|
340
339
|
def queryset(self, request, queryset):
|
|
341
340
|
value = self.value()
|
|
342
|
-
if value
|
|
343
|
-
return queryset
|
|
344
|
-
if value == "scheduled":
|
|
345
|
-
return queryset.scheduled()
|
|
346
|
-
if value == "claimed":
|
|
347
|
-
return queryset.claimed()
|
|
348
|
-
if value == "blocked":
|
|
349
|
-
return queryset.blocked()
|
|
350
|
-
if value == "failed":
|
|
351
|
-
return queryset.failed()
|
|
352
|
-
if value == "finished":
|
|
353
|
-
return queryset.finished()
|
|
341
|
+
if is_queue_state(value):
|
|
342
|
+
return filter_queue_state(queryset, value)
|
|
354
343
|
return queryset
|
|
355
344
|
|
|
356
345
|
|
|
@@ -400,8 +389,10 @@ class JobConcurrencyKeyListFilter(admin.SimpleListFilter):
|
|
|
400
389
|
|
|
401
390
|
def lookups(self, request, model_admin):
|
|
402
391
|
alias = model_admin._backend_database_alias(request)
|
|
392
|
+
backend_alias = model_admin._backend_alias(request)
|
|
403
393
|
return tuple(
|
|
404
394
|
Job.objects.using(alias)
|
|
395
|
+
.filter(backend_alias=backend_alias)
|
|
405
396
|
.exclude(concurrency_key__isnull=True)
|
|
406
397
|
.exclude(concurrency_key="")
|
|
407
398
|
.order_by("concurrency_key")
|
|
@@ -590,15 +581,17 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
590
581
|
def handle_change_action(self, request, obj, action):
|
|
591
582
|
if action == "run_now":
|
|
592
583
|
try:
|
|
593
|
-
_job,
|
|
584
|
+
_job, dispatch_outcome = dispatch_scheduled_job_now(
|
|
585
|
+
obj.pk, backend_alias=obj.backend_alias
|
|
586
|
+
)
|
|
594
587
|
except (EnqueueError, ImportError, AttributeError) as exc:
|
|
595
588
|
self.message_user(request, f"Could not dispatch job now: {exc}", level=messages.ERROR)
|
|
596
589
|
return self._current_object_redirect(obj, backend_alias=obj.backend_alias)
|
|
597
590
|
|
|
598
591
|
message = "Dispatched scheduled job for immediate execution"
|
|
599
|
-
if
|
|
592
|
+
if dispatch_outcome is DispatchOutcome.BLOCKED:
|
|
600
593
|
message = "Dispatched scheduled job immediately and it is now blocked"
|
|
601
|
-
if
|
|
594
|
+
if dispatch_outcome is DispatchOutcome.DISCARDED:
|
|
602
595
|
message = "Dispatched scheduled job immediately and it was discarded"
|
|
603
596
|
self.message_user(request, message, level=messages.SUCCESS)
|
|
604
597
|
return self._current_object_redirect(obj, backend_alias=obj.backend_alias)
|
|
@@ -722,6 +715,7 @@ class FailedExecutionAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
722
715
|
|
|
723
716
|
@admin.register(Process)
|
|
724
717
|
class ProcessAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
718
|
+
backend_filter_field = "backend_alias"
|
|
725
719
|
list_display = (
|
|
726
720
|
"name",
|
|
727
721
|
"backend_alias",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
1
2
|
from functools import partial
|
|
2
3
|
|
|
3
4
|
from django.db.models.functions import Coalesce
|
|
4
5
|
from django.db import transaction
|
|
5
6
|
from django.utils import timezone
|
|
6
7
|
|
|
8
|
+
from dj_queue import observability
|
|
9
|
+
from dj_queue.config import load_backend_config
|
|
7
10
|
from dj_queue.db import get_database_alias
|
|
8
11
|
from dj_queue.models import Pause, ReadyExecution
|
|
9
12
|
from dj_queue.operations.jobs import (
|
|
@@ -50,6 +53,9 @@ class QueueInfo:
|
|
|
50
53
|
|
|
51
54
|
@property
|
|
52
55
|
def latency(self):
|
|
56
|
+
if self.paused:
|
|
57
|
+
return None
|
|
58
|
+
|
|
53
59
|
oldest = (
|
|
54
60
|
self._ready_queryset()
|
|
55
61
|
.annotate(latency_at=Coalesce("latency_started_at", "created_at"))
|
|
@@ -59,7 +65,7 @@ class QueueInfo:
|
|
|
59
65
|
)
|
|
60
66
|
if oldest is None:
|
|
61
67
|
return 0.0
|
|
62
|
-
return (timezone.now() - oldest).total_seconds()
|
|
68
|
+
return max((timezone.now() - oldest).total_seconds(), 0.0)
|
|
63
69
|
|
|
64
70
|
@property
|
|
65
71
|
def paused(self):
|
|
@@ -93,18 +99,15 @@ class QueueInfo:
|
|
|
93
99
|
|
|
94
100
|
@classmethod
|
|
95
101
|
def all(cls, *, backend_alias="default"):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
flat=True,
|
|
104
|
-
)
|
|
105
|
-
.distinct()
|
|
102
|
+
now = timezone.now()
|
|
103
|
+
config = load_backend_config(backend_alias)
|
|
104
|
+
process_cutoff = now - timedelta(seconds=config.process_alive_threshold)
|
|
105
|
+
queue_rows = observability.queue_rows(
|
|
106
|
+
backend_alias=backend_alias,
|
|
107
|
+
now=now,
|
|
108
|
+
process_cutoff=process_cutoff,
|
|
106
109
|
)
|
|
107
|
-
return [cls(
|
|
110
|
+
return [cls(row["name"], backend_alias=backend_alias) for row in queue_rows]
|
|
108
111
|
|
|
109
112
|
def _ready_queryset(self):
|
|
110
113
|
alias = get_database_alias(self.backend_alias)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from asgiref.sync import sync_to_async
|
|
2
|
+
from django.db import close_old_connections, connections
|
|
3
|
+
from django.tasks.backends.base import BaseTaskBackend
|
|
4
|
+
from django.tasks.exceptions import TaskResultDoesNotExist
|
|
5
|
+
|
|
6
|
+
from dj_queue.db import get_database_alias
|
|
7
|
+
from dj_queue.models import Job
|
|
8
|
+
from dj_queue.operations.jobs import (
|
|
9
|
+
DispatchOutcome,
|
|
10
|
+
enqueue_job_with_dispatch,
|
|
11
|
+
enqueue_jobs_bulk,
|
|
12
|
+
validate_priority,
|
|
13
|
+
validate_queue_allowed,
|
|
14
|
+
)
|
|
15
|
+
from dj_queue.task_results import task_result_from_enqueued_job, task_result_from_job
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DjQueueBackend(BaseTaskBackend):
|
|
19
|
+
supports_async_task = True
|
|
20
|
+
supports_defer = True
|
|
21
|
+
supports_get_result = True
|
|
22
|
+
supports_priority = True
|
|
23
|
+
|
|
24
|
+
def validate_task(self, task):
|
|
25
|
+
validate_queue_allowed(task.queue_name, backend_alias=self.alias)
|
|
26
|
+
validate_priority(task.priority)
|
|
27
|
+
return super().validate_task(task)
|
|
28
|
+
|
|
29
|
+
def enqueue(self, task, args, kwargs):
|
|
30
|
+
self.validate_task(task)
|
|
31
|
+
job, dispatch_outcome = enqueue_job_with_dispatch(task, args, kwargs, backend_alias=self.alias)
|
|
32
|
+
return task_result_from_enqueued_job(
|
|
33
|
+
job,
|
|
34
|
+
task,
|
|
35
|
+
successful=dispatch_outcome is DispatchOutcome.DISCARDED,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def aenqueue(self, task, args, kwargs):
|
|
39
|
+
return await sync_to_async(_async_backend_call, thread_sensitive=True)(
|
|
40
|
+
self.enqueue,
|
|
41
|
+
task=task,
|
|
42
|
+
args=args,
|
|
43
|
+
kwargs=kwargs,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def enqueue_all(self, task_calls):
|
|
47
|
+
jobs = []
|
|
48
|
+
for task, args, kwargs in task_calls:
|
|
49
|
+
self.validate_task(task)
|
|
50
|
+
jobs.append((task, args, kwargs))
|
|
51
|
+
|
|
52
|
+
created_jobs = enqueue_jobs_bulk(jobs, backend_alias=self.alias)
|
|
53
|
+
return [
|
|
54
|
+
task_result_from_enqueued_job(
|
|
55
|
+
job,
|
|
56
|
+
task,
|
|
57
|
+
successful=dispatch_outcome is DispatchOutcome.DISCARDED,
|
|
58
|
+
)
|
|
59
|
+
for job, task, dispatch_outcome in created_jobs
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
def get_result(self, result_id):
|
|
63
|
+
alias = get_database_alias(self.alias)
|
|
64
|
+
try:
|
|
65
|
+
job = (
|
|
66
|
+
Job.objects.using(alias)
|
|
67
|
+
.select_related(
|
|
68
|
+
"ready_execution",
|
|
69
|
+
"scheduled_execution",
|
|
70
|
+
"claimed_execution__process",
|
|
71
|
+
"blocked_execution",
|
|
72
|
+
"failed_execution",
|
|
73
|
+
)
|
|
74
|
+
.get(pk=result_id, backend_alias=self.alias)
|
|
75
|
+
)
|
|
76
|
+
except Job.DoesNotExist as exc:
|
|
77
|
+
raise TaskResultDoesNotExist(str(result_id)) from exc
|
|
78
|
+
|
|
79
|
+
return task_result_from_job(job)
|
|
80
|
+
|
|
81
|
+
async def aget_result(self, result_id):
|
|
82
|
+
return await sync_to_async(_async_backend_call, thread_sensitive=True)(
|
|
83
|
+
self.get_result,
|
|
84
|
+
result_id=result_id,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _async_backend_call(method, /, **kwargs):
|
|
89
|
+
close_old_connections()
|
|
90
|
+
try:
|
|
91
|
+
return method(**kwargs)
|
|
92
|
+
finally:
|
|
93
|
+
connections.close_all()
|