Skip to content

Commit b30ca6a

Browse files
gerrod3ggainey
authored andcommitted
Fix redis worker not respecting earlier task resource locks for immediate tasks
1 parent 8f431d0 commit b30ca6a

2 files changed

Lines changed: 34 additions & 7 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a bug where immediate tasks could be executed even if they were blocked by other tasks using the same resources.

pulpcore/tasking/redis_tasks.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from pulpcore.app.models import AppStatus, Task, TaskGroup
1515
from pulpcore.app.redis_connection import get_redis_connection
16-
from pulpcore.constants import TASK_FINAL_STATES, TASK_STATES
16+
from pulpcore.constants import TASK_FINAL_STATES, TASK_INCOMPLETE_STATES, TASK_STATES
1717
from pulpcore.tasking.redis_locks import (
1818
acquire_locks,
1919
async_safe_release_task_locks,
@@ -198,20 +198,33 @@ async def aexecute_task(task):
198198
_logger.debug("Task %s releasing all locks in finally block (async)", task.pk)
199199

200200

201-
def are_resources_available(task: Task, app_lock) -> bool:
201+
def are_resources_available(task: Task, colliding_resources, app_lock) -> bool:
202202
"""
203203
Atomically try to acquire task lock and resource locks for immediate task.
204204
205205
Resource conflicts are handled by Redis lock acquisition - if another task
206206
holds conflicting resource locks, acquire_locks() will fail atomically.
207207
208+
If any earlier task exists that uses a conflicting resource lock then return False.
209+
Otherwise, try to acquire the locks and return True if successful.
210+
208211
Args:
209212
task: The task to acquire locks for.
213+
colliding_resources: The list of resources that can't be taken by other tasks.
210214
app_lock: The current AppStatus instance.
211215
212216
Returns:
213217
bool: True if all locks were acquired, False otherwise.
214218
"""
219+
prior_tasks = Task.objects.filter(
220+
state__in=TASK_INCOMPLETE_STATES, pulp_created__lt=task.pulp_created
221+
)
222+
colliding_resources_taken = prior_tasks.filter(
223+
reserved_resources_record__overlap=colliding_resources
224+
).exists()
225+
if colliding_resources_taken:
226+
return False
227+
215228
redis_conn = get_redis_connection()
216229

217230
# Extract resources from task
@@ -249,20 +262,33 @@ def are_resources_available(task: Task, app_lock) -> bool:
249262
return False
250263

251264

252-
async def async_are_resources_available(task: Task, app_lock) -> bool:
265+
async def async_are_resources_available(task: Task, colliding_resources, app_lock) -> bool:
253266
"""
254267
Atomically try to acquire task lock and resource locks for immediate task (async version).
255268
256269
Resource conflicts are handled by Redis lock acquisition - if another task
257270
holds conflicting resource locks, acquire_locks() will fail atomically.
258271
272+
If any earlier task exists that uses a conflicting resource lock then return False.
273+
Otherwise, try to acquire the locks and return True if successful.
274+
259275
Args:
260276
task: The task to acquire locks for.
277+
colliding_resources: The list of resources that can't be taken by other tasks.
261278
app_lock: The current AppStatus instance.
262279
263280
Returns:
264281
bool: True if all locks were acquired, False otherwise.
265282
"""
283+
prior_tasks = Task.objects.filter(
284+
state__in=TASK_INCOMPLETE_STATES, pulp_created__lt=task.pulp_created
285+
)
286+
colliding_resources_taken = await prior_tasks.filter(
287+
reserved_resources_record__overlap=colliding_resources
288+
).aexists()
289+
if colliding_resources_taken:
290+
return False
291+
266292
redis_conn = get_redis_connection()
267293

268294
# Extract resources from task
@@ -344,7 +370,7 @@ def dispatch(
344370
assert deferred or immediate, "A task must be at least `deferred` or `immediate`."
345371
function_name = get_function_name(func)
346372
versions = get_version(versions, function_name)
347-
_, resources = get_resources(exclusive_resources, shared_resources, immediate)
373+
colliding_resources, resources = get_resources(exclusive_resources, shared_resources, immediate)
348374
app_lock = None if not execute_now else AppStatus.objects.current() # Lazy evaluation...
349375
task_payload = get_task_payload(
350376
function_name, task_group, args, kwargs, resources, versions, immediate, deferred, app_lock
@@ -353,7 +379,7 @@ def dispatch(
353379
if execute_now:
354380
# Try to atomically acquire task lock and resource locks
355381
# are_resources_available() now acquires ALL locks atomically
356-
if are_resources_available(task, app_lock):
382+
if are_resources_available(task, colliding_resources, app_lock):
357383
# All locks acquired successfully
358384
# Proceed with execution
359385
current_app = app_lock
@@ -396,7 +422,7 @@ async def adispatch(
396422
assert deferred or immediate, "A task must be at least `deferred` or `immediate`."
397423
function_name = get_function_name(func)
398424
versions = get_version(versions, function_name)
399-
_, resources = get_resources(exclusive_resources, shared_resources, immediate)
425+
colliding_resources, resources = get_resources(exclusive_resources, shared_resources, immediate)
400426
app_lock = None if not execute_now else AppStatus.objects.current() # Lazy evaluation...
401427
task_payload = get_task_payload(
402428
function_name, task_group, args, kwargs, resources, versions, immediate, deferred, app_lock
@@ -405,7 +431,7 @@ async def adispatch(
405431
if execute_now:
406432
# Try to atomically acquire task lock and resource locks
407433
# async_are_resources_available() now acquires ALL locks atomically
408-
if await async_are_resources_available(task, app_lock):
434+
if await async_are_resources_available(task, colliding_resources, app_lock):
409435
# All locks acquired successfully
410436
# Proceed with execution
411437
current_app = app_lock

0 commit comments

Comments
 (0)