33import shutil
44import subprocess
55import threading
6+ from concurrent .futures import ThreadPoolExecutor , as_completed
67from pathlib import Path
8+ from typing import Optional
79from .Logger import Logger
810from .ParameterManager import ParameterManager
911import sys
@@ -20,20 +22,20 @@ class CommandExecutor:
2022 for execution.
2123 """
2224 # Methods for running commands and logging
23- def __init__ (self , workflow_dir : Path , logger : Logger , parameter_manager : ParameterManager ):
25+ def __init__ (self , workflow_dir : Path , logger : Logger , parameter_manager : ParameterManager , max_threads : Optional [ int ] = None ):
2426 self .pid_dir = Path (workflow_dir , "pids" )
2527 self .logger = logger
2628 self .parameter_manager = parameter_manager
29+ self .max_threads = max_threads
2730
2831 def run_multiple_commands (
2932 self , commands : list [str ]
3033 ) -> bool :
3134 """
32- Executes multiple shell commands concurrently in separate threads .
35+ Executes multiple shell commands concurrently with optional thread limiting .
3336
34- This method leverages threading to run each command in parallel, improving
35- efficiency for batch command execution. Execution time and command results are
36- logged if specified.
37+ Uses ThreadPoolExecutor to run commands in parallel while respecting the
38+ configured maximum thread limit for the current deployment mode.
3739
3840 Args:
3941 commands (list[str]): A list where each element is a list representing
@@ -42,30 +44,28 @@ def run_multiple_commands(
4244 Returns:
4345 bool: True if all commands succeeded, False if any failed.
4446 """
47+ # Determine max workers: use configured limit or fall back to command count (unlimited)
48+ max_workers = self .max_threads if self .max_threads else len (commands )
49+ thread_info = f"max threads: { self .max_threads } " if self .max_threads else "unlimited"
50+
4551 # Log the start of command execution
46- self .logger .log (f"Running { len (commands )} commands in parallel..." , 1 )
52+ self .logger .log (f"Running { len (commands )} commands in parallel ( { thread_info } ) ..." , 1 )
4753 start_time = time .time ()
4854
4955 results = []
50- lock = threading .Lock ()
51-
52- def run_and_track (cmd ):
53- success = self .run_command (cmd )
54- with lock :
55- results .append (success )
56-
57- # Initialize a list to keep track of threads
58- threads = []
59-
60- # Start a new thread for each command
61- for cmd in commands :
62- thread = threading .Thread (target = run_and_track , args = (cmd ,))
63- thread .start ()
64- threads .append (thread )
6556
66- # Wait for all threads to complete
67- for thread in threads :
68- thread .join ()
57+ with ThreadPoolExecutor (max_workers = max_workers ) as executor :
58+ # Submit all commands to the pool
59+ future_to_cmd = {executor .submit (self .run_command , cmd ): cmd for cmd in commands }
60+
61+ # Collect results as they complete
62+ for future in as_completed (future_to_cmd ):
63+ try :
64+ success = future .result ()
65+ results .append (success )
66+ except Exception as e :
67+ self .logger .log (f"ERROR: Command raised exception: { e } " , 0 )
68+ results .append (False )
6969
7070 # Calculate and log the total execution time
7171 end_time = time .time ()
0 commit comments