Run stages in parallel with ThreadPoolExecutor.
(self)
| 63 | shutil.rmtree(self.tmpdir, ignore_errors=True) |
| 64 | |
| 65 | def _run_parallel(self) -> bool: |
| 66 | """Run stages in parallel with ThreadPoolExecutor.""" |
| 67 | assert self.tmpdir is not None, "tmpdir must be initialized before running" |
| 68 | |
| 69 | print("") |
| 70 | print(f"⚡ PARALLEL LINTING ({len(self.stages)} stages)") |
| 71 | print("================================") |
| 72 | |
| 73 | parallel_start = time.time() |
| 74 | failed_stages: list[str] = [] |
| 75 | |
| 76 | # Use ThreadPoolExecutor for I/O-bound workloads |
| 77 | with ThreadPoolExecutor(max_workers=len(self.stages)) as executor: |
| 78 | # Submit all stages (respecting dependencies) |
| 79 | for stage in self.stages: |
| 80 | future = executor.submit(self._run_stage_parallel, stage) |
| 81 | self.stage_futures[stage.name] = future |
| 82 | print(f" Started {stage.name} (PID {os.getpid()})") |
| 83 | |
| 84 | print("") |
| 85 | |
| 86 | # Wait for all stages to complete |
| 87 | for stage in self.stages: |
| 88 | if is_interrupted(): |
| 89 | print("\n⚠️ Interrupted - stopping all stages") |
| 90 | failed_stages.append(stage.name) |
| 91 | continue |
| 92 | |
| 93 | future = self.stage_futures[stage.name] |
| 94 | try: |
| 95 | success, _ = future.result(timeout=stage.timeout) |
| 96 | if success: |
| 97 | print(f" ✅ {stage.name} completed") |
| 98 | else: |
| 99 | print(f" ❌ {stage.name} FAILED") |
| 100 | failed_stages.append(stage.name) |
| 101 | except TimeoutError: |
| 102 | print(f" ❌ {stage.name} TIMEOUT (>{stage.timeout}s)") |
| 103 | failed_stages.append(stage.name) |
| 104 | except KeyboardInterrupt: |
| 105 | _thread.interrupt_main() |
| 106 | raise |
| 107 | except Exception as e: |
| 108 | print(f" ❌ {stage.name} ERROR: {e}") |
| 109 | failed_stages.append(stage.name) |
| 110 | |
| 111 | parallel_duration = time.time() - parallel_start |
| 112 | print("") |
| 113 | print(f" Parallel phase completed in {int(parallel_duration)}s") |
| 114 | |
| 115 | # Print captured output from each stage |
| 116 | for stage in self.stages: |
| 117 | logfile = self.tmpdir / f"{stage.name}.log" |
| 118 | if logfile.exists(): |
| 119 | print("") |
| 120 | print(f"--- {stage.name} output ---") |
| 121 | print(logfile.read_text()) |
| 122 |
no test coverage detected