Reap every process on the way out, whatever the block did.
(processes: list[BaseProcess])
| 13 | |
| 14 | @contextmanager |
| 15 | def cleanup_processes(processes: list[BaseProcess]) -> Generator[None]: |
| 16 | """Reap every process on the way out, whatever the block did.""" |
| 17 | try: |
| 18 | yield |
| 19 | finally: |
| 20 | for process in processes: |
| 21 | # Terminating an unstarted process raises over the assertion that escaped before it started. |
| 22 | if process.pid is not None: |
| 23 | process.terminate() |
| 24 | process.join(timeout=_REAP_DEADLINE) |
| 25 | if process.is_alive(): # pragma: no cover # SIGTERM only loses this race on a saturated runner |
| 26 | process.kill() |
| 27 | process.join(timeout=_REAP_DEADLINE) |
| 28 | # Left open, the finalizer runs during whichever test the collector lands in, as an unraisable exception. |
| 29 | process.close() |
| 30 | |
| 31 | |
| 32 | __all__ = ["cleanup_processes"] |