Пример #1
0
async def stream_subprocess_output(
    process: asyncio.subprocess.Process,
    *,
    timeout: Timeout = None,
    stdout_callback: Optional[OutputStreamCallback] = None,
    stderr_callback: Optional[OutputStreamCallback] = None,
) -> int:
    """
    Asynchronously read the stdout and stderr output streams of a subprocess and
    and optionally invoke a callback with each line of text read.

    :param process: An asyncio subprocess created with `create_subprocess_exec` or `create_subprocess_shell`.
    :param timeout: An optional timeout in seconds for how long to read the streams before giving up.
    :param stdout_callback: An optional callable invoked with each line read from stdout. Must accept a single string positional argument and returns nothing.
    :param stderr_callback: An optional callable invoked with each line read from stderr. Must accept a single string positional argument and returns nothing.

    :raises asyncio.TimeoutError: Raised if the timeout expires before the subprocess exits.
    :return: The exit status of the subprocess.
    """
    tasks = []
    if process.stdout:
        tasks.append(
            asyncio.create_task(
                _read_lines_from_output_stream(process.stdout,
                                               stdout_callback),
                name="stdout",
            ))
    if process.stderr:
        tasks.append(
            asyncio.create_task(
                _read_lines_from_output_stream(process.stderr,
                                               stderr_callback),
                name="stderr",
            ))

    timeout_in_seconds = (timeout.total_seconds() if isinstance(
        timeout, datetime.timedelta) else timeout)
    try:
        # Gather the stream output tasks and the parent process
        gather_task = asyncio.gather(*tasks, process.wait())
        await asyncio.wait_for(gather_task, timeout=timeout_in_seconds)

    except (asyncio.TimeoutError, asyncio.CancelledError):
        with contextlib.suppress(ProcessLookupError):
            if process.returncode is None:
                process.terminate()

        with contextlib.suppress(asyncio.CancelledError):
            await gather_task

        [task.cancel() for task in tasks]
        await asyncio.gather(*tasks, return_exceptions=True)

        raise

    return cast(int, process.returncode)
Пример #2
0
async def terminate_and_wait(proc: asyncio.subprocess.Process) -> None:
    try:
        proc.terminate()
        try:
            await asyncio.wait_for(proc.wait(), timeout=2.0)
        except asyncio.TimeoutError:
            proc.kill()
            await proc.wait()
    except ProcessLookupError:
        pass
Пример #3
0
async def _terminate_process(process: asyncio.subprocess.Process, timeout: int,
                             logger: logging.Logger) -> None:
    returncode = process.returncode
    if returncode is not None:
        logger.info(f"Process has exited with {returncode}")
        return
    logger.info(f"Stopping process with SIGTERM, waiting {timeout} seconds")
    process.terminate()
    try:
        returncode = await asyncio.wait_for(process.wait(), timeout=timeout)
        logger.info(f"Process has exited after SIGTERM with {returncode}")
    except TimeoutError:
        logger.info(
            f"Process hasn't exited after {timeout} seconds, SIGKILL'ing...")
        process.kill()
async def soft_kill(process: asyncio.subprocess.Process) -> None:
    # First try terminating...
    try:
        process.terminate()
        await asyncio.wait_for(process.wait(), timeout=45.0)
        return
    except ProcessLookupError:
        # (can be thrown e.g. if the process has exited in the meantime)
        return
    except asyncio.TimeoutError:
        pass

    # ... then try killing
    try:
        process.kill()
        await process.wait()
    except ProcessLookupError:
        return
Пример #5
0
async def kill_process(
        proc: asyncio.subprocess.Process, wait: float,
        logger: logging.Logger) -> None:  # pylint: disable=no-member
    if proc.returncode is None:
        try:
            proc.terminate()
            await asyncio.sleep(wait)
            if proc.returncode is None:
                try:
                    os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
                except Exception:
                    if proc.returncode is not None:
                        raise
            await proc.wait()
            logger.info("Process killed: retcode=%d", proc.returncode)
        except asyncio.CancelledError:
            pass
        except Exception:
            if proc.returncode is None:
                logger.exception("Can't kill process pid=%d", proc.pid)
            else:
                logger.info("Process killed: retcode=%d", proc.returncode)
Пример #6
0
 async def kill_process(self, proc: asyncio.subprocess.Process):
     proc.terminate()
     try:
         await asyncio.wait_for(proc.wait(), timeout=20)
     except TimeoutError:
         proc.kill()