dj-queue 0.10.2__tar.gz → 0.10.3__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.2 → dj_queue-0.10.3}/PKG-INFO +1 -1
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/concurrency.py +117 -7
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/jobs.py +117 -13
- {dj_queue-0.10.2 → dj_queue-0.10.3}/pyproject.toml +1 -1
- {dj_queue-0.10.2 → dj_queue-0.10.3}/LICENSE +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/README.md +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/admin.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/api.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/apps.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/backend.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/config.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/contrib/prometheus.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/cron.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/dashboard.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/db.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/hooks.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/log.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/metrics.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/observability.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/_helpers.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/_insert.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/queues.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/queue_selectors.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/queue_state.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/routers.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/base.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/connection_budget.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/topology.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/task_results.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/urls.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/views.py +0 -0
- {dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/wakeup.py +0 -0
|
@@ -10,11 +10,20 @@ from dj_queue.config import load_backend_config
|
|
|
10
10
|
from dj_queue.db import database_capabilities, get_database_alias, locked_queryset
|
|
11
11
|
from dj_queue.exceptions import EnqueueError
|
|
12
12
|
from dj_queue.log import log_event
|
|
13
|
-
from dj_queue.models import
|
|
13
|
+
from dj_queue.models import (
|
|
14
|
+
BlockedExecution,
|
|
15
|
+
ClaimedExecution,
|
|
16
|
+
FailedExecution,
|
|
17
|
+
Job,
|
|
18
|
+
ReadyExecution,
|
|
19
|
+
ScheduledExecution,
|
|
20
|
+
Semaphore,
|
|
21
|
+
)
|
|
14
22
|
from dj_queue.operations._helpers import (
|
|
15
23
|
_consume_selected_rows,
|
|
16
24
|
_create_blocked_execution,
|
|
17
25
|
_create_ready_execution,
|
|
26
|
+
_lock_active_pauses,
|
|
18
27
|
_task_option,
|
|
19
28
|
)
|
|
20
29
|
from dj_queue.operations._insert import create_ignore_conflicts
|
|
@@ -42,7 +51,7 @@ def semaphore_acquire(
|
|
|
42
51
|
now=now,
|
|
43
52
|
)
|
|
44
53
|
|
|
45
|
-
with
|
|
54
|
+
with _operation_atomic(alias):
|
|
46
55
|
if create_ignore_conflicts(
|
|
47
56
|
Semaphore,
|
|
48
57
|
using=alias,
|
|
@@ -54,7 +63,7 @@ def semaphore_acquire(
|
|
|
54
63
|
return True
|
|
55
64
|
|
|
56
65
|
reconciled_available = _reconciled_available_expression(limit)
|
|
57
|
-
with
|
|
66
|
+
with _operation_atomic(alias):
|
|
58
67
|
updated = (
|
|
59
68
|
Semaphore.objects.using(alias)
|
|
60
69
|
.filter(key=key, value__gt=F("limit") - Value(limit))
|
|
@@ -177,6 +186,29 @@ def _consume_released_semaphore_slot(alias, key, *, limit, duration_seconds, now
|
|
|
177
186
|
return updated > 0
|
|
178
187
|
|
|
179
188
|
|
|
189
|
+
def _handoff_released_claimed_slot(alias, key, *, limit, duration_seconds, now):
|
|
190
|
+
expires_at = now + timedelta(seconds=duration_seconds)
|
|
191
|
+
released_available = _released_available_expression(limit)
|
|
192
|
+
updated = (
|
|
193
|
+
Semaphore.objects.using(alias)
|
|
194
|
+
.filter(key=key, value__gt=F("limit") - Value(limit) - Value(1))
|
|
195
|
+
.update(
|
|
196
|
+
value=released_available - Value(1),
|
|
197
|
+
limit=limit,
|
|
198
|
+
expires_at=expires_at,
|
|
199
|
+
updated_at=now,
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
return updated > 0
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _released_available_expression(limit):
|
|
206
|
+
return Least(
|
|
207
|
+
Value(limit),
|
|
208
|
+
Greatest(Value(0), F("value") + Value(limit) - F("limit") + Value(1)),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
180
212
|
def _reconciled_available_expression(limit):
|
|
181
213
|
return Least(Value(limit), Greatest(Value(0), F("value") + Value(limit) - F("limit")))
|
|
182
214
|
|
|
@@ -209,11 +241,12 @@ def unblock_next_blocked_job(
|
|
|
209
241
|
backend_alias="default",
|
|
210
242
|
use_skip_locked=True,
|
|
211
243
|
handoff_released_slot=False,
|
|
244
|
+
release_slot=False,
|
|
212
245
|
):
|
|
213
246
|
alias = get_database_alias(backend_alias)
|
|
214
247
|
now = timezone.now()
|
|
215
248
|
|
|
216
|
-
with
|
|
249
|
+
with _operation_atomic(alias):
|
|
217
250
|
queryset = (
|
|
218
251
|
BlockedExecution.objects.using(alias)
|
|
219
252
|
.select_related("job")
|
|
@@ -229,7 +262,15 @@ def unblock_next_blocked_job(
|
|
|
229
262
|
return None
|
|
230
263
|
|
|
231
264
|
slot_acquired = False
|
|
232
|
-
if
|
|
265
|
+
if release_slot:
|
|
266
|
+
slot_acquired = _handoff_released_claimed_slot(
|
|
267
|
+
alias,
|
|
268
|
+
key,
|
|
269
|
+
limit=limit,
|
|
270
|
+
duration_seconds=duration_seconds,
|
|
271
|
+
now=now,
|
|
272
|
+
)
|
|
273
|
+
elif handoff_released_slot:
|
|
233
274
|
slot_acquired = _consume_released_semaphore_slot(
|
|
234
275
|
alias,
|
|
235
276
|
key,
|
|
@@ -238,7 +279,7 @@ def unblock_next_blocked_job(
|
|
|
238
279
|
now=now,
|
|
239
280
|
)
|
|
240
281
|
|
|
241
|
-
if not slot_acquired:
|
|
282
|
+
if not slot_acquired and not release_slot:
|
|
242
283
|
slot_acquired = semaphore_acquire(
|
|
243
284
|
key,
|
|
244
285
|
limit=limit,
|
|
@@ -261,7 +302,7 @@ def unblock_next_blocked_job(
|
|
|
261
302
|
job = blocked.job
|
|
262
303
|
queue_name = blocked.queue_name
|
|
263
304
|
priority = blocked.priority
|
|
264
|
-
|
|
305
|
+
_create_ready_execution_after_blocked_consume(
|
|
265
306
|
alias,
|
|
266
307
|
job=job,
|
|
267
308
|
backend_alias=backend_alias,
|
|
@@ -279,6 +320,71 @@ def unblock_next_blocked_job(
|
|
|
279
320
|
return job
|
|
280
321
|
|
|
281
322
|
|
|
323
|
+
def _create_ready_execution_after_blocked_consume(
|
|
324
|
+
alias,
|
|
325
|
+
*,
|
|
326
|
+
job,
|
|
327
|
+
backend_alias,
|
|
328
|
+
queue_name,
|
|
329
|
+
priority,
|
|
330
|
+
ready_at,
|
|
331
|
+
):
|
|
332
|
+
_lock_active_pauses(alias, backend_alias, {queue_name})
|
|
333
|
+
connection = connections[alias]
|
|
334
|
+
quote = connection.ops.quote_name
|
|
335
|
+
ready_table = quote(ReadyExecution._meta.db_table)
|
|
336
|
+
job_id_column = quote(ReadyExecution._meta.get_field("job").column)
|
|
337
|
+
backend_alias_column = quote(ReadyExecution._meta.get_field("backend_alias").column)
|
|
338
|
+
queue_name_column = quote(ReadyExecution._meta.get_field("queue_name").column)
|
|
339
|
+
priority_column = quote(ReadyExecution._meta.get_field("priority").column)
|
|
340
|
+
created_at_column = quote(ReadyExecution._meta.get_field("created_at").column)
|
|
341
|
+
latency_started_at_column = quote(ReadyExecution._meta.get_field("latency_started_at").column)
|
|
342
|
+
job_id = Job._meta.get_field("id").get_db_prep_value(
|
|
343
|
+
job.pk,
|
|
344
|
+
connection=connection,
|
|
345
|
+
prepared=False,
|
|
346
|
+
)
|
|
347
|
+
state_models = (ReadyExecution, ScheduledExecution, ClaimedExecution, FailedExecution)
|
|
348
|
+
state_checks = " AND ".join(
|
|
349
|
+
_state_absence_sql(model, job_id_column=job_id_column, quote=quote) for model in state_models
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
with connection.cursor() as cursor:
|
|
353
|
+
cursor.execute(
|
|
354
|
+
f"""
|
|
355
|
+
INSERT INTO {ready_table} (
|
|
356
|
+
{job_id_column},
|
|
357
|
+
{backend_alias_column},
|
|
358
|
+
{queue_name_column},
|
|
359
|
+
{priority_column},
|
|
360
|
+
{created_at_column},
|
|
361
|
+
{latency_started_at_column}
|
|
362
|
+
)
|
|
363
|
+
SELECT %s, %s, %s, %s, %s, %s
|
|
364
|
+
WHERE {state_checks}
|
|
365
|
+
""",
|
|
366
|
+
[
|
|
367
|
+
job_id,
|
|
368
|
+
backend_alias,
|
|
369
|
+
queue_name,
|
|
370
|
+
priority,
|
|
371
|
+
ready_at,
|
|
372
|
+
ready_at,
|
|
373
|
+
*([job_id] * len(state_models)),
|
|
374
|
+
],
|
|
375
|
+
)
|
|
376
|
+
created = cursor.rowcount
|
|
377
|
+
|
|
378
|
+
if created != 1:
|
|
379
|
+
raise EnqueueError(f"job {job.id} already has an execution-state row")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _state_absence_sql(model, *, job_id_column, quote):
|
|
383
|
+
state_table = quote(model._meta.db_table)
|
|
384
|
+
state_job_id_column = quote(model._meta.get_field("job").column)
|
|
385
|
+
return f"NOT EXISTS (SELECT 1 FROM {state_table} WHERE {state_table}.{state_job_id_column} = %s)"
|
|
386
|
+
|
|
387
|
+
|
|
282
388
|
def cleanup_expired_semaphores(*, backend_alias="default"):
|
|
283
389
|
alias = get_database_alias(backend_alias)
|
|
284
390
|
claimed_concurrency_keys = (
|
|
@@ -397,3 +503,7 @@ def _positive_int_option(value, name):
|
|
|
397
503
|
if number <= 0:
|
|
398
504
|
raise EnqueueError(f"{name} must be a positive integer")
|
|
399
505
|
return number
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _operation_atomic(alias):
|
|
509
|
+
return transaction.atomic(using=alias, savepoint=not connections[alias].in_atomic_block)
|
|
@@ -32,7 +32,6 @@ from dj_queue.operations._helpers import (
|
|
|
32
32
|
_ensure_no_other_execution_state,
|
|
33
33
|
_consume_selected_rows,
|
|
34
34
|
_create_blocked_execution,
|
|
35
|
-
_create_ready_execution,
|
|
36
35
|
_create_ready_execution_locked,
|
|
37
36
|
_create_scheduled_execution,
|
|
38
37
|
_exclude_active_pauses,
|
|
@@ -110,7 +109,12 @@ def enqueue_job_with_dispatch(task, args, kwargs, *, backend_alias="default"):
|
|
|
110
109
|
scheduled_at=task.run_after,
|
|
111
110
|
concurrency_key=concurrency_key,
|
|
112
111
|
)
|
|
113
|
-
dispatch_outcome = _dispatch_job(
|
|
112
|
+
dispatch_outcome = _dispatch_job(
|
|
113
|
+
job,
|
|
114
|
+
task=task,
|
|
115
|
+
backend_alias=backend_alias,
|
|
116
|
+
check_conflicts=False,
|
|
117
|
+
)
|
|
114
118
|
|
|
115
119
|
if dispatch_outcome.should_notify:
|
|
116
120
|
notify_ready_queues_on_commit((job.queue_name,), backend_alias=backend_alias)
|
|
@@ -429,15 +433,13 @@ def _complete_claimed_job(job, return_value, *, backend_alias="default", task=No
|
|
|
429
433
|
|
|
430
434
|
with transaction.atomic(using=alias):
|
|
431
435
|
_delete_claimed_execution(alias, job.id)
|
|
432
|
-
_ensure_no_other_execution_state(alias, job, ignored_models=(ClaimedExecution,))
|
|
433
436
|
now = timezone.now()
|
|
434
437
|
config = load_backend_config(job.backend_alias)
|
|
435
438
|
|
|
436
439
|
if config.preserve_finished_jobs:
|
|
437
|
-
job
|
|
438
|
-
job.return_value = return_value
|
|
439
|
-
job.save(using=alias, update_fields=["finished_at", "return_value", "updated_at"])
|
|
440
|
+
_finish_job_if_no_execution_state(alias, job, return_value, finished_at=now)
|
|
440
441
|
else:
|
|
442
|
+
_ensure_no_other_execution_state(alias, job, ignored_models=(ClaimedExecution,))
|
|
441
443
|
job.delete(using=alias)
|
|
442
444
|
|
|
443
445
|
_release_concurrency_slot(job, task=task)
|
|
@@ -910,7 +912,7 @@ def _dispatch_existing_job(job):
|
|
|
910
912
|
return _dispatch_job(job, task=task, backend_alias=job.backend_alias)
|
|
911
913
|
|
|
912
914
|
|
|
913
|
-
def _dispatch_job(job, *, task, backend_alias, now=None):
|
|
915
|
+
def _dispatch_job(job, *, task, backend_alias, now=None, check_conflicts=True):
|
|
914
916
|
alias = get_database_alias(backend_alias)
|
|
915
917
|
if now is None:
|
|
916
918
|
now = timezone.now()
|
|
@@ -925,11 +927,13 @@ def _dispatch_job(job, *, task, backend_alias, now=None):
|
|
|
925
927
|
return DispatchOutcome.SCHEDULED
|
|
926
928
|
|
|
927
929
|
if not job.concurrency_key:
|
|
928
|
-
|
|
930
|
+
_create_ready_execution_locked(
|
|
929
931
|
alias,
|
|
930
932
|
job=job,
|
|
931
933
|
backend_alias=backend_alias,
|
|
934
|
+
queue_name=job.queue_name,
|
|
932
935
|
ready_at=now,
|
|
936
|
+
check_conflicts=check_conflicts,
|
|
933
937
|
)
|
|
934
938
|
return DispatchOutcome.READY
|
|
935
939
|
|
|
@@ -979,6 +983,19 @@ def _release_concurrency_slot(job, *, task=None):
|
|
|
979
983
|
limit = _semaphore_limit(job) or 1
|
|
980
984
|
duration_seconds = config.default_concurrency_duration
|
|
981
985
|
|
|
986
|
+
if (
|
|
987
|
+
unblock_next_blocked_job(
|
|
988
|
+
job.concurrency_key,
|
|
989
|
+
limit=limit,
|
|
990
|
+
duration_seconds=duration_seconds,
|
|
991
|
+
backend_alias=job.backend_alias,
|
|
992
|
+
use_skip_locked=config.use_skip_locked,
|
|
993
|
+
release_slot=True,
|
|
994
|
+
)
|
|
995
|
+
is not None
|
|
996
|
+
):
|
|
997
|
+
return
|
|
998
|
+
|
|
982
999
|
semaphore_release(
|
|
983
1000
|
job.concurrency_key,
|
|
984
1001
|
limit=limit,
|
|
@@ -1064,11 +1081,19 @@ def _select_ready_rows(queryset, *, limit, queues, use_skip_locked):
|
|
|
1064
1081
|
ordered_selectors = selectors if star_index is None else selectors[:star_index]
|
|
1065
1082
|
|
|
1066
1083
|
if ordered_selectors:
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1084
|
+
if _selectors_are_exact(ordered_selectors):
|
|
1085
|
+
rows = _select_exact_selector_rows(
|
|
1086
|
+
queryset.exclude(pk__in=selected_ids),
|
|
1087
|
+
ordered_selectors,
|
|
1088
|
+
limit=limit,
|
|
1089
|
+
use_skip_locked=use_skip_locked,
|
|
1090
|
+
)
|
|
1091
|
+
else:
|
|
1092
|
+
ordered = _ordered_selector_rows_queryset(
|
|
1093
|
+
queryset.exclude(pk__in=selected_ids),
|
|
1094
|
+
ordered_selectors,
|
|
1095
|
+
)
|
|
1096
|
+
rows = list(locked_queryset(ordered, use_skip_locked=use_skip_locked)[:limit])
|
|
1072
1097
|
selected_rows.extend(rows)
|
|
1073
1098
|
selected_ids.update(row.pk for row in rows)
|
|
1074
1099
|
|
|
@@ -1082,6 +1107,22 @@ def _select_ready_rows(queryset, *, limit, queues, use_skip_locked):
|
|
|
1082
1107
|
return selected_rows
|
|
1083
1108
|
|
|
1084
1109
|
|
|
1110
|
+
def _select_exact_selector_rows(queryset, selectors, *, limit, use_skip_locked):
|
|
1111
|
+
selected_rows = []
|
|
1112
|
+
for selector in dict.fromkeys(selectors):
|
|
1113
|
+
remaining = limit - len(selected_rows)
|
|
1114
|
+
if remaining <= 0:
|
|
1115
|
+
break
|
|
1116
|
+
ordered = queryset.filter(queue_name=selector).order_by("-priority", "id")
|
|
1117
|
+
rows = list(locked_queryset(ordered, use_skip_locked=use_skip_locked)[:remaining])
|
|
1118
|
+
selected_rows.extend(rows)
|
|
1119
|
+
return selected_rows
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _selectors_are_exact(selectors):
|
|
1123
|
+
return all(selector != "*" and not selector.endswith("*") for selector in selectors)
|
|
1124
|
+
|
|
1125
|
+
|
|
1085
1126
|
def _ordered_selector_rows_queryset(queryset, selectors):
|
|
1086
1127
|
filtered = _filter_queue_selectors(queryset, selectors)
|
|
1087
1128
|
selector_rank = Case(
|
|
@@ -1141,6 +1182,69 @@ def _delete_claimed_execution(alias, job_id):
|
|
|
1141
1182
|
raise ClaimedExecution.DoesNotExist
|
|
1142
1183
|
|
|
1143
1184
|
|
|
1185
|
+
def _finish_job_if_no_execution_state(alias, job, return_value, *, finished_at):
|
|
1186
|
+
connection = connections[alias]
|
|
1187
|
+
quote = connection.ops.quote_name
|
|
1188
|
+
jobs_table = quote(Job._meta.db_table)
|
|
1189
|
+
job_id_column = quote(Job._meta.get_field("id").column)
|
|
1190
|
+
backend_alias_column = quote(Job._meta.get_field("backend_alias").column)
|
|
1191
|
+
finished_at_column = quote(Job._meta.get_field("finished_at").column)
|
|
1192
|
+
return_value_column = quote(Job._meta.get_field("return_value").column)
|
|
1193
|
+
updated_at_column = quote(Job._meta.get_field("updated_at").column)
|
|
1194
|
+
state_checks = " AND ".join(
|
|
1195
|
+
_state_absence_sql(model, jobs_table=jobs_table, job_id_column=job_id_column, quote=quote)
|
|
1196
|
+
for model in (
|
|
1197
|
+
ReadyExecution,
|
|
1198
|
+
ScheduledExecution,
|
|
1199
|
+
BlockedExecution,
|
|
1200
|
+
FailedExecution,
|
|
1201
|
+
)
|
|
1202
|
+
)
|
|
1203
|
+
job_id = Job._meta.get_field("id").get_db_prep_value(
|
|
1204
|
+
job.pk,
|
|
1205
|
+
connection=connection,
|
|
1206
|
+
prepared=False,
|
|
1207
|
+
)
|
|
1208
|
+
prepared_return_value = Job._meta.get_field("return_value").get_db_prep_save(
|
|
1209
|
+
return_value,
|
|
1210
|
+
connection=connection,
|
|
1211
|
+
)
|
|
1212
|
+
|
|
1213
|
+
with connection.cursor() as cursor:
|
|
1214
|
+
cursor.execute(
|
|
1215
|
+
f"""
|
|
1216
|
+
UPDATE {jobs_table}
|
|
1217
|
+
SET
|
|
1218
|
+
{finished_at_column} = %s,
|
|
1219
|
+
{return_value_column} = %s,
|
|
1220
|
+
{updated_at_column} = %s
|
|
1221
|
+
WHERE
|
|
1222
|
+
{jobs_table}.{job_id_column} = %s
|
|
1223
|
+
AND {jobs_table}.{backend_alias_column} = %s
|
|
1224
|
+
AND {state_checks}
|
|
1225
|
+
""",
|
|
1226
|
+
[finished_at, prepared_return_value, finished_at, job_id, job.backend_alias],
|
|
1227
|
+
)
|
|
1228
|
+
updated = cursor.rowcount
|
|
1229
|
+
|
|
1230
|
+
if updated != 1:
|
|
1231
|
+
raise EnqueueError(f"job {job.id} already has an execution-state row")
|
|
1232
|
+
job.finished_at = finished_at
|
|
1233
|
+
job.return_value = return_value
|
|
1234
|
+
job.updated_at = finished_at
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def _state_absence_sql(model, *, jobs_table, job_id_column, quote):
|
|
1238
|
+
state_table = quote(model._meta.db_table)
|
|
1239
|
+
state_job_id_column = quote(model._meta.get_field("job").column)
|
|
1240
|
+
return (
|
|
1241
|
+
f"NOT EXISTS ("
|
|
1242
|
+
f"SELECT 1 FROM {state_table} "
|
|
1243
|
+
f"WHERE {state_table}.{state_job_id_column} = {jobs_table}.{job_id_column}"
|
|
1244
|
+
f")"
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
|
|
1144
1248
|
def _bulk_create(alias, model, objects):
|
|
1145
1249
|
if not objects:
|
|
1146
1250
|
return None
|
|
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.2 → dj_queue-0.10.3}/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
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html
RENAMED
|
File without changes
|
{dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html
RENAMED
|
File without changes
|
{dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html
RENAMED
|
File without changes
|
{dj_queue-0.10.2 → dj_queue-0.10.3}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.10.2 → dj_queue-0.10.3}/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.2 → dj_queue-0.10.3}/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
|