dj-queue 0.10.3__tar.gz → 0.10.4__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.10.3 → dj_queue-0.10.4}/PKG-INFO +6 -7
- {dj_queue-0.10.3 → dj_queue-0.10.4}/README.md +5 -6
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/backend.py +8 -2
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/_helpers.py +8 -3
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/concurrency.py +111 -26
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/jobs.py +210 -43
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/base.py +9 -2
- {dj_queue-0.10.3 → dj_queue-0.10.4}/pyproject.toml +1 -1
- {dj_queue-0.10.3 → dj_queue-0.10.4}/LICENSE +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/admin.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/api.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/apps.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/config.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/contrib/prometheus.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/cron.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/dashboard.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/db.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/hooks.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/log.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/metrics.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/observability.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/_insert.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/queues.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/queue_selectors.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/queue_state.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/routers.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/connection_budget.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/topology.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/task_results.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/urls.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/views.py +0 -0
- {dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/wakeup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.4
|
|
4
4
|
Summary: Database-backed task queue backend for Django’s Tasks framework.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -61,6 +61,9 @@ It has a narrow, explicit shape:
|
|
|
61
61
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
62
62
|
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
63
63
|
|
|
64
|
+
For benchmarks on enqueue, promotion, recurring, claiming, worker-drain, and concurrency-contention scenarios,
|
|
65
|
+
see [docs/benchmarks/](docs/benchmarks/) for the latest published reports.
|
|
66
|
+
|
|
64
67
|
## Installation
|
|
65
68
|
|
|
66
69
|
`dj_queue` requires Python 3.12+ and Django 6.0+.
|
|
@@ -640,8 +643,9 @@ Run your normal application migrations on `default`, then migrate `dj_queue`
|
|
|
640
643
|
onto the queue database:
|
|
641
644
|
|
|
642
645
|
```bash
|
|
643
|
-
|
|
646
|
+
# migrate dj_queue on its queue alias first so django doesn't mark it applied on default
|
|
644
647
|
python manage.py migrate dj_queue --database queue
|
|
648
|
+
python manage.py migrate
|
|
645
649
|
```
|
|
646
650
|
|
|
647
651
|
With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
|
|
@@ -1012,11 +1016,6 @@ Both endpoints support bearer token authentication. Set
|
|
|
1012
1016
|
`Authorization: Bearer <token>`. Leave it unset if you protect these URLs at
|
|
1013
1017
|
the network or proxy layer.
|
|
1014
1018
|
|
|
1015
|
-
## Benchmarks
|
|
1016
|
-
|
|
1017
|
-
The repository includes a standalone benchmark harness for enqueue, promotion, recurring, worker-drain, and concurrency-contention scenarios.
|
|
1018
|
-
See [`docs/benchmarks/`](docs/benchmarks/) for the latest published reports.
|
|
1019
|
-
|
|
1020
1019
|
## License
|
|
1021
1020
|
|
|
1022
1021
|
MIT
|
|
@@ -35,6 +35,9 @@ It has a narrow, explicit shape:
|
|
|
35
35
|
For detailed comparisons with Celery, RQ, Procrastinate, and other alternatives,
|
|
36
36
|
see [COMPARISONS.md](docs/COMPARISONS.md).
|
|
37
37
|
|
|
38
|
+
For benchmarks on enqueue, promotion, recurring, claiming, worker-drain, and concurrency-contention scenarios,
|
|
39
|
+
see [docs/benchmarks/](docs/benchmarks/) for the latest published reports.
|
|
40
|
+
|
|
38
41
|
## Installation
|
|
39
42
|
|
|
40
43
|
`dj_queue` requires Python 3.12+ and Django 6.0+.
|
|
@@ -614,8 +617,9 @@ Run your normal application migrations on `default`, then migrate `dj_queue`
|
|
|
614
617
|
onto the queue database:
|
|
615
618
|
|
|
616
619
|
```bash
|
|
617
|
-
|
|
620
|
+
# migrate dj_queue on its queue alias first so django doesn't mark it applied on default
|
|
618
621
|
python manage.py migrate dj_queue --database queue
|
|
622
|
+
python manage.py migrate
|
|
619
623
|
```
|
|
620
624
|
|
|
621
625
|
With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
|
|
@@ -986,11 +990,6 @@ Both endpoints support bearer token authentication. Set
|
|
|
986
990
|
`Authorization: Bearer <token>`. Leave it unset if you protect these URLs at
|
|
987
991
|
the network or proxy layer.
|
|
988
992
|
|
|
989
|
-
## Benchmarks
|
|
990
|
-
|
|
991
|
-
The repository includes a standalone benchmark harness for enqueue, promotion, recurring, worker-drain, and concurrency-contention scenarios.
|
|
992
|
-
See [`docs/benchmarks/`](docs/benchmarks/) for the latest published reports.
|
|
993
|
-
|
|
994
993
|
## License
|
|
995
994
|
|
|
996
995
|
MIT
|
|
@@ -28,7 +28,13 @@ class DjQueueBackend(BaseTaskBackend):
|
|
|
28
28
|
|
|
29
29
|
def enqueue(self, task, args, kwargs):
|
|
30
30
|
self.validate_task(task)
|
|
31
|
-
job, dispatch_outcome = enqueue_job_with_dispatch(
|
|
31
|
+
job, dispatch_outcome = enqueue_job_with_dispatch(
|
|
32
|
+
task,
|
|
33
|
+
args,
|
|
34
|
+
kwargs,
|
|
35
|
+
backend_alias=self.alias,
|
|
36
|
+
validate=False,
|
|
37
|
+
)
|
|
32
38
|
return task_result_from_enqueued_job(
|
|
33
39
|
job,
|
|
34
40
|
task,
|
|
@@ -49,7 +55,7 @@ class DjQueueBackend(BaseTaskBackend):
|
|
|
49
55
|
self.validate_task(task)
|
|
50
56
|
jobs.append((task, args, kwargs))
|
|
51
57
|
|
|
52
|
-
created_jobs = enqueue_jobs_bulk(jobs, backend_alias=self.alias)
|
|
58
|
+
created_jobs = enqueue_jobs_bulk(jobs, backend_alias=self.alias, validate=False)
|
|
53
59
|
return [
|
|
54
60
|
task_result_from_enqueued_job(
|
|
55
61
|
job,
|
|
@@ -170,8 +170,11 @@ def _scheduled_execution_row(job, *, backend_alias, scheduled_at=None, created_a
|
|
|
170
170
|
)
|
|
171
171
|
|
|
172
172
|
|
|
173
|
-
def _create_scheduled_execution(
|
|
174
|
-
|
|
173
|
+
def _create_scheduled_execution(
|
|
174
|
+
alias, job, *, backend_alias, scheduled_at=None, check_conflicts=True
|
|
175
|
+
):
|
|
176
|
+
if check_conflicts:
|
|
177
|
+
_ensure_no_other_execution_state(alias, job)
|
|
175
178
|
return ScheduledExecution.objects.using(alias).create(
|
|
176
179
|
**_scheduled_execution_fields(
|
|
177
180
|
job,
|
|
@@ -190,8 +193,10 @@ def _create_blocked_execution(
|
|
|
190
193
|
expires_at,
|
|
191
194
|
queue_name=None,
|
|
192
195
|
priority=None,
|
|
196
|
+
check_conflicts=True,
|
|
193
197
|
):
|
|
194
|
-
|
|
198
|
+
if check_conflicts:
|
|
199
|
+
_ensure_no_other_execution_state(alias, job)
|
|
195
200
|
return BlockedExecution.objects.using(alias).create(
|
|
196
201
|
**_blocked_execution_fields(
|
|
197
202
|
job,
|
|
@@ -22,7 +22,7 @@ from dj_queue.models import (
|
|
|
22
22
|
from dj_queue.operations._helpers import (
|
|
23
23
|
_consume_selected_rows,
|
|
24
24
|
_create_blocked_execution,
|
|
25
|
-
|
|
25
|
+
_create_ready_execution_locked,
|
|
26
26
|
_lock_active_pauses,
|
|
27
27
|
_task_option,
|
|
28
28
|
)
|
|
@@ -247,20 +247,15 @@ def unblock_next_blocked_job(
|
|
|
247
247
|
now = timezone.now()
|
|
248
248
|
|
|
249
249
|
with _operation_atomic(alias):
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
blocked = _consume_next_blocked_job(
|
|
251
|
+
alias,
|
|
252
|
+
backend_alias=backend_alias,
|
|
253
|
+
key=key,
|
|
254
|
+
use_skip_locked=use_skip_locked,
|
|
255
255
|
)
|
|
256
|
-
blocked = locked_queryset(queryset, use_skip_locked=use_skip_locked).first()
|
|
257
256
|
if blocked is None:
|
|
258
257
|
return None
|
|
259
258
|
|
|
260
|
-
consumed = _consume_selected_rows(alias, BlockedExecution, [blocked])
|
|
261
|
-
if not consumed:
|
|
262
|
-
return None
|
|
263
|
-
|
|
264
259
|
slot_acquired = False
|
|
265
260
|
if release_slot:
|
|
266
261
|
slot_acquired = _handoff_released_claimed_slot(
|
|
@@ -287,37 +282,125 @@ def unblock_next_blocked_job(
|
|
|
287
282
|
backend_alias=backend_alias,
|
|
288
283
|
)
|
|
289
284
|
|
|
285
|
+
mock_job = Job(
|
|
286
|
+
id=blocked["job_id"],
|
|
287
|
+
queue_name=blocked["queue_name"],
|
|
288
|
+
priority=blocked["priority"],
|
|
289
|
+
concurrency_key=blocked["concurrency_key"],
|
|
290
|
+
backend_alias=backend_alias,
|
|
291
|
+
)
|
|
292
|
+
|
|
290
293
|
if not slot_acquired:
|
|
291
294
|
_create_blocked_execution(
|
|
292
295
|
alias,
|
|
293
|
-
|
|
296
|
+
mock_job,
|
|
294
297
|
backend_alias=backend_alias,
|
|
295
|
-
queue_name=blocked
|
|
296
|
-
priority=blocked
|
|
297
|
-
concurrency_key=blocked
|
|
298
|
-
expires_at=blocked
|
|
298
|
+
queue_name=blocked["queue_name"],
|
|
299
|
+
priority=blocked["priority"],
|
|
300
|
+
concurrency_key=blocked["concurrency_key"],
|
|
301
|
+
expires_at=blocked["expires_at"],
|
|
302
|
+
check_conflicts=False,
|
|
299
303
|
)
|
|
300
304
|
return None
|
|
301
305
|
|
|
302
|
-
job = blocked.job
|
|
303
|
-
queue_name = blocked.queue_name
|
|
304
|
-
priority = blocked.priority
|
|
305
306
|
_create_ready_execution_after_blocked_consume(
|
|
306
307
|
alias,
|
|
307
|
-
job=
|
|
308
|
+
job=mock_job,
|
|
308
309
|
backend_alias=backend_alias,
|
|
309
|
-
queue_name=queue_name,
|
|
310
|
-
priority=priority,
|
|
310
|
+
queue_name=blocked["queue_name"],
|
|
311
|
+
priority=blocked["priority"],
|
|
311
312
|
ready_at=now,
|
|
312
313
|
)
|
|
313
314
|
|
|
314
315
|
log_event(
|
|
315
316
|
"job.unblocked",
|
|
316
|
-
job_id=str(
|
|
317
|
+
job_id=str(mock_job.id),
|
|
317
318
|
concurrency_key=key,
|
|
318
319
|
)
|
|
319
|
-
notify_ready_queues_on_commit((
|
|
320
|
-
return
|
|
320
|
+
notify_ready_queues_on_commit((blocked["queue_name"],), backend_alias=backend_alias)
|
|
321
|
+
return mock_job
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _consume_next_blocked_job(alias, *, backend_alias, key, use_skip_locked):
|
|
325
|
+
capabilities = database_capabilities(alias)
|
|
326
|
+
if capabilities.backend_family == "postgresql":
|
|
327
|
+
return _postgres_consume_next_blocked_job(
|
|
328
|
+
alias,
|
|
329
|
+
backend_alias=backend_alias,
|
|
330
|
+
key=key,
|
|
331
|
+
use_skip_locked=use_skip_locked and capabilities.supports_skip_locked,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
queryset = (
|
|
335
|
+
BlockedExecution.objects.using(alias)
|
|
336
|
+
.filter(backend_alias=backend_alias, concurrency_key=key)
|
|
337
|
+
.order_by("-priority", "id")
|
|
338
|
+
)
|
|
339
|
+
blocked = locked_queryset(queryset, use_skip_locked=use_skip_locked).first()
|
|
340
|
+
if blocked is None:
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
consumed = _consume_selected_rows(alias, BlockedExecution, [blocked])
|
|
344
|
+
if not consumed:
|
|
345
|
+
return None
|
|
346
|
+
return _blocked_execution_values(blocked)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _postgres_consume_next_blocked_job(alias, *, backend_alias, key, use_skip_locked):
|
|
350
|
+
connection = connections[alias]
|
|
351
|
+
quote = connection.ops.quote_name
|
|
352
|
+
table = quote(BlockedExecution._meta.db_table)
|
|
353
|
+
pk_column = quote(BlockedExecution._meta.pk.column)
|
|
354
|
+
job_id_column = quote(BlockedExecution._meta.get_field("job").column)
|
|
355
|
+
backend_alias_column = quote(BlockedExecution._meta.get_field("backend_alias").column)
|
|
356
|
+
queue_name_column = quote(BlockedExecution._meta.get_field("queue_name").column)
|
|
357
|
+
priority_column = quote(BlockedExecution._meta.get_field("priority").column)
|
|
358
|
+
concurrency_key_column = quote(BlockedExecution._meta.get_field("concurrency_key").column)
|
|
359
|
+
expires_at_column = quote(BlockedExecution._meta.get_field("expires_at").column)
|
|
360
|
+
skip_locked_sql = " SKIP LOCKED" if use_skip_locked else ""
|
|
361
|
+
|
|
362
|
+
with connection.cursor() as cursor:
|
|
363
|
+
cursor.execute(
|
|
364
|
+
f"""
|
|
365
|
+
DELETE FROM {table}
|
|
366
|
+
WHERE {table}.{pk_column} = (
|
|
367
|
+
SELECT {pk_column}
|
|
368
|
+
FROM {table}
|
|
369
|
+
WHERE {backend_alias_column} = %s AND {concurrency_key_column} = %s
|
|
370
|
+
ORDER BY {priority_column} DESC, {pk_column} ASC
|
|
371
|
+
LIMIT 1
|
|
372
|
+
FOR UPDATE{skip_locked_sql}
|
|
373
|
+
)
|
|
374
|
+
RETURNING
|
|
375
|
+
{job_id_column},
|
|
376
|
+
{queue_name_column},
|
|
377
|
+
{priority_column},
|
|
378
|
+
{concurrency_key_column},
|
|
379
|
+
{expires_at_column}
|
|
380
|
+
""",
|
|
381
|
+
[backend_alias, key],
|
|
382
|
+
)
|
|
383
|
+
row = cursor.fetchone()
|
|
384
|
+
|
|
385
|
+
if row is None:
|
|
386
|
+
return None
|
|
387
|
+
return {
|
|
388
|
+
"job_id": row[0],
|
|
389
|
+
"queue_name": row[1],
|
|
390
|
+
"priority": row[2],
|
|
391
|
+
"concurrency_key": row[3],
|
|
392
|
+
"expires_at": row[4],
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _blocked_execution_values(blocked):
|
|
397
|
+
return {
|
|
398
|
+
"job_id": blocked.job_id,
|
|
399
|
+
"queue_name": blocked.queue_name,
|
|
400
|
+
"priority": blocked.priority,
|
|
401
|
+
"concurrency_key": blocked.concurrency_key,
|
|
402
|
+
"expires_at": blocked.expires_at,
|
|
403
|
+
}
|
|
321
404
|
|
|
322
405
|
|
|
323
406
|
def _create_ready_execution_after_blocked_consume(
|
|
@@ -455,13 +538,14 @@ def promote_expired_blocked_jobs(*, batch_size=500, backend_alias="default", use
|
|
|
455
538
|
priority = blocked.priority
|
|
456
539
|
if not uses_serialized_writes:
|
|
457
540
|
blocked.delete(using=alias)
|
|
458
|
-
|
|
541
|
+
_create_ready_execution_locked(
|
|
459
542
|
alias,
|
|
460
543
|
job=job,
|
|
461
544
|
backend_alias=backend_alias,
|
|
462
545
|
queue_name=queue_name,
|
|
463
546
|
priority=priority,
|
|
464
547
|
ready_at=now,
|
|
548
|
+
check_conflicts=False,
|
|
465
549
|
)
|
|
466
550
|
promoted_jobs.append(job)
|
|
467
551
|
else:
|
|
@@ -475,6 +559,7 @@ def promote_expired_blocked_jobs(*, batch_size=500, backend_alias="default", use
|
|
|
475
559
|
priority=blocked.priority,
|
|
476
560
|
concurrency_key=blocked.concurrency_key,
|
|
477
561
|
expires_at=expires_at,
|
|
562
|
+
check_conflicts=False,
|
|
478
563
|
)
|
|
479
564
|
else:
|
|
480
565
|
blocked.expires_at = expires_at
|
|
@@ -6,7 +6,6 @@ from dataclasses import dataclass
|
|
|
6
6
|
from datetime import timedelta
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
|
|
9
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
10
9
|
from django.db import connections, transaction
|
|
11
10
|
from django.db.models import Case, IntegerField, Value, When
|
|
12
11
|
from django.db.utils import OperationalError
|
|
@@ -15,7 +14,7 @@ from django.utils import timezone
|
|
|
15
14
|
from django.utils.module_loading import import_string
|
|
16
15
|
|
|
17
16
|
from dj_queue.config import load_allowed_queues, load_backend_config
|
|
18
|
-
from dj_queue.db import get_database_alias, locked_queryset
|
|
17
|
+
from dj_queue.db import database_capabilities, get_database_alias, locked_queryset
|
|
19
18
|
from dj_queue.exceptions import EnqueueError
|
|
20
19
|
from dj_queue.log import event_logging_enabled, log_event
|
|
21
20
|
from dj_queue.models import (
|
|
@@ -92,9 +91,10 @@ def enqueue_job(task, args, kwargs, *, backend_alias="default"):
|
|
|
92
91
|
return job
|
|
93
92
|
|
|
94
93
|
|
|
95
|
-
def enqueue_job_with_dispatch(task, args, kwargs, *, backend_alias="default"):
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
def enqueue_job_with_dispatch(task, args, kwargs, *, backend_alias="default", validate=True):
|
|
95
|
+
if validate:
|
|
96
|
+
validate_queue_allowed(task.queue_name, backend_alias=backend_alias)
|
|
97
|
+
validate_priority(task.priority)
|
|
98
98
|
alias = get_database_alias(backend_alias)
|
|
99
99
|
payload = _normalize_payload(args, kwargs)
|
|
100
100
|
concurrency_key = _resolve_concurrency_key(task, args, kwargs)
|
|
@@ -130,14 +130,15 @@ def enqueue_job_with_dispatch(task, args, kwargs, *, backend_alias="default"):
|
|
|
130
130
|
return job, dispatch_outcome
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
def enqueue_jobs_bulk(task_calls, *, backend_alias="default"):
|
|
133
|
+
def enqueue_jobs_bulk(task_calls, *, backend_alias="default", validate=True):
|
|
134
134
|
alias = get_database_alias(backend_alias)
|
|
135
135
|
now = timezone.now()
|
|
136
136
|
prepared = []
|
|
137
137
|
|
|
138
138
|
for index, (task, args, kwargs) in enumerate(task_calls):
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if validate:
|
|
140
|
+
validate_queue_allowed(task.queue_name, backend_alias=backend_alias)
|
|
141
|
+
validate_priority(task.priority)
|
|
141
142
|
payload = _normalize_payload(args, kwargs)
|
|
142
143
|
concurrency_key = _resolve_concurrency_key(task, args, kwargs)
|
|
143
144
|
created_at = now + timedelta(microseconds=index)
|
|
@@ -318,16 +319,9 @@ def _claim_ready_jobs_once(
|
|
|
318
319
|
):
|
|
319
320
|
|
|
320
321
|
with transaction.atomic(using=alias):
|
|
322
|
+
claimed_insert_checks_conflicts = _claimed_insert_checks_conflicts(alias)
|
|
321
323
|
queryset = (
|
|
322
|
-
ReadyExecution.objects.using(alias)
|
|
323
|
-
.select_related(
|
|
324
|
-
"job",
|
|
325
|
-
"job__scheduled_execution",
|
|
326
|
-
"job__claimed_execution",
|
|
327
|
-
"job__blocked_execution",
|
|
328
|
-
"job__failed_execution",
|
|
329
|
-
)
|
|
330
|
-
.filter(backend_alias=backend_alias)
|
|
324
|
+
ReadyExecution.objects.using(alias).select_related("job").filter(backend_alias=backend_alias)
|
|
331
325
|
)
|
|
332
326
|
queryset = _exclude_active_pauses(queryset, alias, backend_alias)
|
|
333
327
|
ready_rows = _select_ready_rows(
|
|
@@ -339,9 +333,15 @@ def _claim_ready_jobs_once(
|
|
|
339
333
|
if not ready_rows:
|
|
340
334
|
return []
|
|
341
335
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
if not claimed_insert_checks_conflicts:
|
|
337
|
+
conflicting_job_ids = _job_ids_with_other_execution_state(
|
|
338
|
+
alias,
|
|
339
|
+
[row.job_id for row in ready_rows],
|
|
340
|
+
ignored_models=(ReadyExecution,),
|
|
341
|
+
)
|
|
342
|
+
if conflicting_job_ids:
|
|
343
|
+
conflicting_job_id = next(iter(conflicting_job_ids))
|
|
344
|
+
raise EnqueueError(f"job {conflicting_job_id} already has an execution-state row")
|
|
345
345
|
|
|
346
346
|
paused_queue_names = _lock_active_pauses(
|
|
347
347
|
alias,
|
|
@@ -361,30 +361,17 @@ def _claim_ready_jobs_once(
|
|
|
361
361
|
|
|
362
362
|
claimed_at = timezone.now()
|
|
363
363
|
worker_ids = (process.name,) if process is not None else ()
|
|
364
|
-
|
|
364
|
+
_create_claimed_executions(
|
|
365
365
|
alias,
|
|
366
|
-
|
|
367
|
-
|
|
366
|
+
jobs,
|
|
367
|
+
process=process,
|
|
368
|
+
claimed_at=claimed_at,
|
|
369
|
+
check_conflicts=claimed_insert_checks_conflicts,
|
|
368
370
|
)
|
|
369
371
|
|
|
370
372
|
return [ClaimedJob(job=job, claimed_at=claimed_at, worker_ids=worker_ids) for job in jobs]
|
|
371
373
|
|
|
372
374
|
|
|
373
|
-
def _ready_row_has_conflicting_state(row):
|
|
374
|
-
for relation_name in (
|
|
375
|
-
"scheduled_execution",
|
|
376
|
-
"claimed_execution",
|
|
377
|
-
"blocked_execution",
|
|
378
|
-
"failed_execution",
|
|
379
|
-
):
|
|
380
|
-
try:
|
|
381
|
-
getattr(row.job, relation_name)
|
|
382
|
-
except ObjectDoesNotExist:
|
|
383
|
-
continue
|
|
384
|
-
return True
|
|
385
|
-
return False
|
|
386
|
-
|
|
387
|
-
|
|
388
375
|
def execute_claimed_job(job, *, backend_alias="default"):
|
|
389
376
|
claimed_job = None
|
|
390
377
|
if isinstance(job, ClaimedJob):
|
|
@@ -432,13 +419,22 @@ def _complete_claimed_job(job, return_value, *, backend_alias="default", task=No
|
|
|
432
419
|
job = _resolve_claimed_job(job, alias=alias, backend_alias=backend_alias)
|
|
433
420
|
|
|
434
421
|
with transaction.atomic(using=alias):
|
|
435
|
-
_delete_claimed_execution(alias, job.id)
|
|
436
422
|
now = timezone.now()
|
|
437
423
|
config = load_backend_config(job.backend_alias)
|
|
438
424
|
|
|
439
425
|
if config.preserve_finished_jobs:
|
|
440
|
-
|
|
426
|
+
if database_capabilities(alias).backend_family == "postgresql":
|
|
427
|
+
_delete_claimed_and_finish_job_if_no_execution_state(
|
|
428
|
+
alias,
|
|
429
|
+
job,
|
|
430
|
+
return_value,
|
|
431
|
+
finished_at=now,
|
|
432
|
+
)
|
|
433
|
+
else:
|
|
434
|
+
_delete_claimed_execution(alias, job.id)
|
|
435
|
+
_finish_job_if_no_execution_state(alias, job, return_value, finished_at=now)
|
|
441
436
|
else:
|
|
437
|
+
_delete_claimed_execution(alias, job.id)
|
|
442
438
|
_ensure_no_other_execution_state(alias, job, ignored_models=(ClaimedExecution,))
|
|
443
439
|
job.delete(using=alias)
|
|
444
440
|
|
|
@@ -674,7 +670,7 @@ def promote_scheduled_jobs(*, batch_size, backend_alias="default", use_skip_lock
|
|
|
674
670
|
for job in jobs:
|
|
675
671
|
if job.pk in direct_job_ids:
|
|
676
672
|
continue
|
|
677
|
-
dispatch_outcome = _dispatch_existing_job(job)
|
|
673
|
+
dispatch_outcome = _dispatch_existing_job(job, check_conflicts=False)
|
|
678
674
|
if dispatch_outcome.should_notify:
|
|
679
675
|
ready_queue_names.append(job.queue_name)
|
|
680
676
|
|
|
@@ -907,9 +903,11 @@ def discard_blocked_jobs(*, job_ids=None, batch_size=500, backend_alias="default
|
|
|
907
903
|
)
|
|
908
904
|
|
|
909
905
|
|
|
910
|
-
def _dispatch_existing_job(job):
|
|
906
|
+
def _dispatch_existing_job(job, *, check_conflicts=True):
|
|
911
907
|
task = import_string(job.task_path)
|
|
912
|
-
return _dispatch_job(
|
|
908
|
+
return _dispatch_job(
|
|
909
|
+
job, task=task, backend_alias=job.backend_alias, check_conflicts=check_conflicts
|
|
910
|
+
)
|
|
913
911
|
|
|
914
912
|
|
|
915
913
|
def _dispatch_job(job, *, task, backend_alias, now=None, check_conflicts=True):
|
|
@@ -923,6 +921,7 @@ def _dispatch_job(job, *, task, backend_alias, now=None, check_conflicts=True):
|
|
|
923
921
|
job=job,
|
|
924
922
|
backend_alias=backend_alias,
|
|
925
923
|
scheduled_at=job.scheduled_at,
|
|
924
|
+
check_conflicts=check_conflicts,
|
|
926
925
|
)
|
|
927
926
|
return DispatchOutcome.SCHEDULED
|
|
928
927
|
|
|
@@ -966,6 +965,7 @@ def _dispatch_job(job, *, task, backend_alias, now=None, check_conflicts=True):
|
|
|
966
965
|
backend_alias=backend_alias,
|
|
967
966
|
concurrency_key=job.concurrency_key,
|
|
968
967
|
expires_at=now + timedelta(seconds=duration_seconds),
|
|
968
|
+
check_conflicts=check_conflicts,
|
|
969
969
|
)
|
|
970
970
|
return DispatchOutcome.BLOCKED
|
|
971
971
|
|
|
@@ -1107,6 +1107,105 @@ def _select_ready_rows(queryset, *, limit, queues, use_skip_locked):
|
|
|
1107
1107
|
return selected_rows
|
|
1108
1108
|
|
|
1109
1109
|
|
|
1110
|
+
def _claimed_insert_checks_conflicts(alias):
|
|
1111
|
+
return database_capabilities(alias).backend_family == "postgresql"
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def _create_claimed_executions(alias, jobs, *, process, claimed_at, check_conflicts):
|
|
1115
|
+
if check_conflicts:
|
|
1116
|
+
return _postgres_create_claimed_executions_if_no_other_state(
|
|
1117
|
+
alias,
|
|
1118
|
+
jobs,
|
|
1119
|
+
process=process,
|
|
1120
|
+
claimed_at=claimed_at,
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
return _bulk_create(
|
|
1124
|
+
alias,
|
|
1125
|
+
ClaimedExecution,
|
|
1126
|
+
[ClaimedExecution(job=job, process=process, created_at=claimed_at) for job in jobs],
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
def _postgres_create_claimed_executions_if_no_other_state(
|
|
1131
|
+
alias,
|
|
1132
|
+
jobs,
|
|
1133
|
+
*,
|
|
1134
|
+
process,
|
|
1135
|
+
claimed_at,
|
|
1136
|
+
):
|
|
1137
|
+
connection = connections[alias]
|
|
1138
|
+
quote = connection.ops.quote_name
|
|
1139
|
+
claimed_table = quote(ClaimedExecution._meta.db_table)
|
|
1140
|
+
job_id_column = quote(ClaimedExecution._meta.get_field("job").column)
|
|
1141
|
+
process_id_column = quote(ClaimedExecution._meta.get_field("process").column)
|
|
1142
|
+
created_at_column = quote(ClaimedExecution._meta.get_field("created_at").column)
|
|
1143
|
+
values_sql = ", ".join(["(%s::uuid, %s::bigint, %s::timestamptz)"] * len(jobs))
|
|
1144
|
+
state_checks = " AND ".join(
|
|
1145
|
+
_state_absence_for_job_id_sql(
|
|
1146
|
+
model,
|
|
1147
|
+
job_id_reference="claimed_input.job_id",
|
|
1148
|
+
quote=quote,
|
|
1149
|
+
)
|
|
1150
|
+
for model in (
|
|
1151
|
+
ReadyExecution,
|
|
1152
|
+
ScheduledExecution,
|
|
1153
|
+
ClaimedExecution,
|
|
1154
|
+
BlockedExecution,
|
|
1155
|
+
FailedExecution,
|
|
1156
|
+
)
|
|
1157
|
+
)
|
|
1158
|
+
params = []
|
|
1159
|
+
process_id = process.pk if process is not None else None
|
|
1160
|
+
job_id_field = Job._meta.get_field("id")
|
|
1161
|
+
for job in jobs:
|
|
1162
|
+
params.extend(
|
|
1163
|
+
[
|
|
1164
|
+
job_id_field.get_db_prep_value(job.pk, connection=connection, prepared=False),
|
|
1165
|
+
process_id,
|
|
1166
|
+
claimed_at,
|
|
1167
|
+
]
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
with connection.cursor() as cursor:
|
|
1171
|
+
cursor.execute(
|
|
1172
|
+
f"""
|
|
1173
|
+
INSERT INTO {claimed_table} (
|
|
1174
|
+
{job_id_column},
|
|
1175
|
+
{process_id_column},
|
|
1176
|
+
{created_at_column}
|
|
1177
|
+
)
|
|
1178
|
+
SELECT
|
|
1179
|
+
claimed_input.job_id,
|
|
1180
|
+
claimed_input.process_id,
|
|
1181
|
+
claimed_input.created_at
|
|
1182
|
+
FROM (VALUES {values_sql}) AS claimed_input(job_id, process_id, created_at)
|
|
1183
|
+
WHERE {state_checks}
|
|
1184
|
+
""",
|
|
1185
|
+
params,
|
|
1186
|
+
)
|
|
1187
|
+
created = cursor.rowcount
|
|
1188
|
+
|
|
1189
|
+
if created != len(jobs):
|
|
1190
|
+
conflicting_job_ids = _job_ids_with_other_execution_state(alias, [job.pk for job in jobs])
|
|
1191
|
+
if conflicting_job_ids:
|
|
1192
|
+
conflicting_job_id = next(iter(conflicting_job_ids))
|
|
1193
|
+
raise EnqueueError(f"job {conflicting_job_id} already has an execution-state row")
|
|
1194
|
+
raise EnqueueError("could not claim selected jobs")
|
|
1195
|
+
return None
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
def _state_absence_for_job_id_sql(model, *, job_id_reference, quote):
|
|
1199
|
+
state_table = quote(model._meta.db_table)
|
|
1200
|
+
state_job_id_column = quote(model._meta.get_field("job").column)
|
|
1201
|
+
return (
|
|
1202
|
+
f"NOT EXISTS ("
|
|
1203
|
+
f"SELECT 1 FROM {state_table} "
|
|
1204
|
+
f"WHERE {state_table}.{state_job_id_column} = {job_id_reference}"
|
|
1205
|
+
f")"
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
|
|
1110
1209
|
def _select_exact_selector_rows(queryset, selectors, *, limit, use_skip_locked):
|
|
1111
1210
|
selected_rows = []
|
|
1112
1211
|
for selector in dict.fromkeys(selectors):
|
|
@@ -1182,6 +1281,74 @@ def _delete_claimed_execution(alias, job_id):
|
|
|
1182
1281
|
raise ClaimedExecution.DoesNotExist
|
|
1183
1282
|
|
|
1184
1283
|
|
|
1284
|
+
def _delete_claimed_and_finish_job_if_no_execution_state(alias, job, return_value, *, finished_at):
|
|
1285
|
+
connection = connections[alias]
|
|
1286
|
+
quote = connection.ops.quote_name
|
|
1287
|
+
claimed_table = quote(ClaimedExecution._meta.db_table)
|
|
1288
|
+
claimed_job_id_column = quote(ClaimedExecution._meta.get_field("job").column)
|
|
1289
|
+
jobs_table = quote(Job._meta.db_table)
|
|
1290
|
+
job_id_column = quote(Job._meta.get_field("id").column)
|
|
1291
|
+
backend_alias_column = quote(Job._meta.get_field("backend_alias").column)
|
|
1292
|
+
finished_at_column = quote(Job._meta.get_field("finished_at").column)
|
|
1293
|
+
return_value_column = quote(Job._meta.get_field("return_value").column)
|
|
1294
|
+
updated_at_column = quote(Job._meta.get_field("updated_at").column)
|
|
1295
|
+
state_checks = " AND ".join(
|
|
1296
|
+
_state_absence_sql(model, jobs_table=jobs_table, job_id_column=job_id_column, quote=quote)
|
|
1297
|
+
for model in (
|
|
1298
|
+
ReadyExecution,
|
|
1299
|
+
ScheduledExecution,
|
|
1300
|
+
BlockedExecution,
|
|
1301
|
+
FailedExecution,
|
|
1302
|
+
)
|
|
1303
|
+
)
|
|
1304
|
+
job_id = Job._meta.get_field("id").get_db_prep_value(
|
|
1305
|
+
job.pk,
|
|
1306
|
+
connection=connection,
|
|
1307
|
+
prepared=False,
|
|
1308
|
+
)
|
|
1309
|
+
prepared_return_value = Job._meta.get_field("return_value").get_db_prep_save(
|
|
1310
|
+
return_value,
|
|
1311
|
+
connection=connection,
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
with connection.cursor() as cursor:
|
|
1315
|
+
cursor.execute(
|
|
1316
|
+
f"""
|
|
1317
|
+
WITH deleted_claim AS (
|
|
1318
|
+
DELETE FROM {claimed_table}
|
|
1319
|
+
WHERE {claimed_table}.{claimed_job_id_column} = %s
|
|
1320
|
+
RETURNING {claimed_job_id_column}
|
|
1321
|
+
),
|
|
1322
|
+
updated_job AS (
|
|
1323
|
+
UPDATE {jobs_table}
|
|
1324
|
+
SET
|
|
1325
|
+
{finished_at_column} = %s,
|
|
1326
|
+
{return_value_column} = %s,
|
|
1327
|
+
{updated_at_column} = %s
|
|
1328
|
+
WHERE
|
|
1329
|
+
{jobs_table}.{job_id_column} = %s
|
|
1330
|
+
AND {jobs_table}.{backend_alias_column} = %s
|
|
1331
|
+
AND EXISTS (SELECT 1 FROM deleted_claim)
|
|
1332
|
+
AND {state_checks}
|
|
1333
|
+
RETURNING {job_id_column}
|
|
1334
|
+
)
|
|
1335
|
+
SELECT
|
|
1336
|
+
(SELECT COUNT(*) FROM deleted_claim),
|
|
1337
|
+
(SELECT COUNT(*) FROM updated_job)
|
|
1338
|
+
""",
|
|
1339
|
+
[job_id, finished_at, prepared_return_value, finished_at, job_id, job.backend_alias],
|
|
1340
|
+
)
|
|
1341
|
+
deleted_count, updated_count = cursor.fetchone()
|
|
1342
|
+
|
|
1343
|
+
if deleted_count != 1:
|
|
1344
|
+
raise ClaimedExecution.DoesNotExist
|
|
1345
|
+
if updated_count != 1:
|
|
1346
|
+
raise EnqueueError(f"job {job.id} already has an execution-state row")
|
|
1347
|
+
job.finished_at = finished_at
|
|
1348
|
+
job.return_value = return_value
|
|
1349
|
+
job.updated_at = finished_at
|
|
1350
|
+
|
|
1351
|
+
|
|
1185
1352
|
def _finish_job_if_no_execution_state(alias, job, return_value, *, finished_at):
|
|
1186
1353
|
connection = connections[alias]
|
|
1187
1354
|
quote = connection.ops.quote_name
|
|
@@ -244,16 +244,23 @@ class BaseRunner:
|
|
|
244
244
|
if interval <= 0:
|
|
245
245
|
return
|
|
246
246
|
self._heartbeat_stop_event.clear()
|
|
247
|
-
self._heartbeat_thread = threading.Thread(
|
|
247
|
+
self._heartbeat_thread = threading.Thread(
|
|
248
|
+
target=self._heartbeat_loop,
|
|
249
|
+
name=f"dj_queue-heartbeat-{self.name}",
|
|
250
|
+
daemon=True,
|
|
251
|
+
)
|
|
248
252
|
self._heartbeat_thread.start()
|
|
249
253
|
|
|
250
254
|
def _stop_heartbeat_thread(self):
|
|
251
255
|
thread = self._heartbeat_thread
|
|
252
256
|
if thread is None:
|
|
253
|
-
return
|
|
257
|
+
return True
|
|
254
258
|
self._heartbeat_stop_event.set()
|
|
255
259
|
thread.join(timeout=max(self._effective_heartbeat_interval(), 0.1) + 0.1)
|
|
260
|
+
if thread.is_alive():
|
|
261
|
+
return False
|
|
256
262
|
self._heartbeat_thread = None
|
|
263
|
+
return True
|
|
257
264
|
|
|
258
265
|
def _heartbeat_loop(self):
|
|
259
266
|
interval = self._effective_heartbeat_interval()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html
RENAMED
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html
RENAMED
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html
RENAMED
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.3 → dj_queue-0.10.4}/dj_queue/templates/admin/dj_queue/includes/fieldset.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|