1313
1414from pulpcore .app .models import AppStatus , Task , TaskGroup
1515from 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
1717from 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