| # Copyright 2024 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from __future__ import annotations |
| |
| import contextlib |
| import logging |
| from subprocess import Popen, TimeoutExpired |
| from typing import TYPE_CHECKING, Final, Optional |
| |
| import psutil |
| |
| if TYPE_CHECKING: |
| from crossbench.plt.base import Platform, ProcessLike |
| from crossbench.plt.signals import Signals |
| |
| PROCESS_NOT_FOUND_EXCEPTIONS: Final = (psutil.NoSuchProcess, |
| psutil.AccessDenied, |
| psutil.ZombieProcess, ProcessLookupError) |
| |
| |
| def terminate_gracefully(platform: Platform, |
| process: ProcessLike, |
| timeout: int = 1, |
| signal: Optional[Signals] = None) -> None: |
| """Graceful process termination |
| 1. Send the provided signal or SIGTERM by default |
| 2. Wait for the process to terminate |
| 3. Kill the process |
| """ |
| if not signal: |
| signal = platform.signals.SIGTERM |
| |
| try: |
| platform.send_signal(process, signal) |
| # TODO(392938079): support timeout on more process types |
| if isinstance(process, Popen): |
| process.wait(timeout) |
| return |
| except TimeoutExpired as e: |
| logging.debug("Got timeout while waiting " |
| "for process shutdown (%s): %s", process, e) |
| except PROCESS_NOT_FOUND_EXCEPTIONS as e: # noqa: BLE001 |
| logging.debug("Ignoring exception during process termination: %s", e) |
| finally: |
| with contextlib.suppress(*PROCESS_NOT_FOUND_EXCEPTIONS): |
| platform.kill(process) |