11import logging
22
3+ from celery import chord
34from django .core .files .uploadedfile import TemporaryUploadedFile
45from django .core .serializers import serialize
56from django .db .models .query_utils import Q
67from django .urls import reverse
78
89import dojo .jira_link .helper as jira_helper
10+ from dojo import utils
911from dojo .decorators import we_want_async
1012from dojo .finding import helper as finding_helper
1113from dojo .importers .base_importer import BaseImporter , Parser
1719 Test_Import ,
1820)
1921from dojo .notifications .helper import create_notification
20- from dojo .tasks import wait_for_tasks_and_calculate_grade
2122from dojo .utils import calculate_grade
2223from dojo .validators import clean_tags
2324
@@ -158,7 +159,11 @@ def process_findings(
158159 parsed_findings : list [Finding ],
159160 ** kwargs : dict ,
160161 ) -> list [Finding ]:
161- async_task_ids = []
162+ # Progressive batching for chord execution
163+ post_processing_task_signatures = []
164+ current_batch_number = 1
165+ max_batch_size = 1024
166+ pending_grade_calculations = []
162167
163168 """
164169 Saves findings in memory that were parsed from the scan report into the database.
@@ -248,10 +253,25 @@ def process_findings(
248253 push_to_jira = push_to_jira ,
249254 )
250255
251- # We need to call apply_async to get the result of the task so we can collect the task ID
252256 if we_want_async (async_user = self .user ):
253- result = post_processing_task_signature .apply_async ()
254- async_task_ids .append (result .id )
257+ # Collect signatures for progressive batch execution
258+ post_processing_task_signatures .append (post_processing_task_signature )
259+
260+ # Calculate current batch size: 2^batch_number, capped at max_batch_size
261+ current_batch_size = min (2 ** current_batch_number , max_batch_size )
262+
263+ # Launch chord when batch is full
264+ if len (post_processing_task_signatures ) >= current_batch_size :
265+ product = self .test .engagement .product
266+ calculate_grade_signature = utils .calculate_grade_signature (product )
267+ chord_result = chord (post_processing_task_signatures )(calculate_grade_signature )
268+ pending_grade_calculations .append (chord_result )
269+
270+ logger .debug (f"Launched chord with { len (post_processing_task_signatures )} tasks (batch #{ current_batch_number } , size: { current_batch_size } )" )
271+
272+ # Reset for next batch
273+ post_processing_task_signatures = []
274+ current_batch_number += 1
255275 else :
256276 # Execute task immediately for synchronous processing
257277 post_processing_task_signature ()
@@ -270,14 +290,18 @@ def process_findings(
270290 else :
271291 jira_helper .push_to_jira (findings [0 ])
272292
273- # Calculate product grade after all findings are processed
293+ # Handle any remaining signatures in the final batch
274294 product = self .test .engagement .product
275295
276- if we_want_async (async_user = self .user ) and async_task_ids :
277- # Tasks were executed immediately during processing, now coordinate final grade calculation
278- wait_for_tasks_and_calculate_grade .delay (async_task_ids , product .id )
296+ if we_want_async (async_user = self .user ):
297+ if post_processing_task_signatures :
298+ # Launch final chord with remaining signatures
299+ calculate_grade_signature = utils .calculate_grade_signature (product )
300+ chord_result = chord (post_processing_task_signatures )(calculate_grade_signature )
301+ pending_grade_calculations .append (chord_result )
302+ logger .debug (f"Launched final chord with { len (post_processing_task_signatures )} remaining tasks" )
279303
280- # Synchronous tasks were already executed during processing, just calculate grade
304+ # Always perform an initial grading, even though it might get overwritten alter.
281305 calculate_grade (product )
282306
283307 sync = kwargs .get ("sync" , True )
0 commit comments