@@ -111,6 +111,7 @@ def __init__(self, worker_id: int, runner: "RunWorkers") -> None:
111111 self .worker_id = worker_id
112112 self .runtests = runner .runtests
113113 self .pending = runner .pending
114+ self .worker_gate = runner .worker_gate
114115 self .output = runner .output
115116 self .timeout = runner .worker_timeout
116117 self .log = runner .log
@@ -415,6 +416,8 @@ def run(self) -> None:
415416 for test_name in case_ids :
416417 if self ._stopped :
417418 break
419+ if self .worker_gate is not None and not self .worker_gate .acquire ():
420+ raise ExitThread
418421 self .start_time = time .monotonic ()
419422 self .test_name = test_name
420423 try :
@@ -425,6 +428,8 @@ def run(self) -> None:
425428 mp_result = exc .mp_result
426429 finally :
427430 self .test_name = _NOT_RUNNING
431+ if self .worker_gate is not None :
432+ self .worker_gate .release ()
428433 mp_result .result .duration = time .monotonic () - self .start_time
429434 if single_process_per_case :
430435 # Report the test case, not the test module
@@ -501,7 +506,7 @@ def get_running(workers: list[WorkerThread]) -> str | None:
501506class RunWorkers :
502507 def __init__ (self , num_workers : int , runtests : RunTests ,
503508 logger : Logger , results : TestResults ,
504- reporter : Reporter ) -> None :
509+ reporter : Reporter , adaptive_target : float | None = None ) -> None :
505510 self .num_workers = num_workers
506511 self .runtests = runtests
507512 self .logger = logger
@@ -529,6 +534,13 @@ def __init__(self, num_workers: int, runtests: RunTests,
529534 # these worker threads would never get anything to do.
530535 self .num_workers = min (self .num_workers , jobs )
531536
537+ self .worker_gate = None
538+ if adaptive_target is not None :
539+ from .adaptive_workers import AdaptiveWorkerGate
540+ self .worker_gate = AdaptiveWorkerGate (
541+ adaptive_target , max (1 , self .num_workers ), logger )
542+ self .next_worker_update = time .monotonic () + self .worker_gate .update_interval
543+
532544 def start_workers (self ) -> None :
533545 self .workers = [WorkerThread (index , self )
534546 for index in range (1 , self .num_workers + 1 )]
@@ -545,13 +557,18 @@ def start_workers(self) -> None:
545557 msg += (" (timeout: %s, worker timeout: %s)"
546558 % (format_duration (self .timeout ),
547559 format_duration (self .worker_timeout )))
560+ if self .worker_gate is not None :
561+ msg += (f' (Load target: { self .worker_gate .target :.0%} , adaptive workers; '
562+ f'load average { self .worker_gate .load_target :.2f} )' )
548563 self .log (msg )
549564 for worker in self .workers :
550565 worker .start ()
551566 self .live_worker_count += 1
552567
553568 def stop_workers (self ) -> None :
554569 start_time = time .monotonic ()
570+ if self .worker_gate is not None :
571+ self .worker_gate .stop ()
555572 for worker in self .workers :
556573 worker .stop ()
557574 for worker in self .workers :
@@ -568,9 +585,17 @@ def _get_result(self) -> QueueOutput | None:
568585 faulthandler .dump_traceback_later (MAIN_PROCESS_TIMEOUT ,
569586 exit = True )
570587
588+ timeout = PROGRESS_UPDATE
589+ if self .worker_gate is not None :
590+ now = time .monotonic ()
591+ if now >= self .next_worker_update :
592+ self .worker_gate .update ()
593+ self .next_worker_update = now + self .worker_gate .update_interval
594+ timeout = min (timeout , self .next_worker_update - now )
595+
571596 # wait for a thread
572597 try :
573- result = self .output .get (timeout = PROGRESS_UPDATE )
598+ result = self .output .get (timeout = timeout )
574599 if isinstance (result , WorkerThreadExited ):
575600 self .live_worker_count -= 1
576601 continue
@@ -647,6 +672,8 @@ def run(self) -> None:
647672 fail_fast = self .runtests .fail_fast
648673 fail_env_changed = self .runtests .fail_env_changed
649674 self .logger .get_mem_usage = self .get_mem_usage
675+ if self .worker_gate is not None :
676+ self .logger .get_active_workers = self .worker_gate .get_active_workers
650677
651678 self .start_workers ()
652679
@@ -672,3 +699,4 @@ def run(self) -> None:
672699 self .pending .stop ()
673700 self .stop_workers ()
674701 self .logger .get_mem_usage = None
702+ self .logger .get_active_workers = None
0 commit comments