Convert command_line to use asyncio for subprocesses (#111927)

* Convert command_line to use asyncio for subprocesses

* fixes

* fix

* fixes

* more test fixes

* more fixes

* fixes

* preen
This commit is contained in:
J. Nick Koston 2024-03-01 18:15:10 -10:00 committed by GitHub
parent 5f65315e86
commit c0f7ade92b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 115 additions and 117 deletions

View file

@ -1,13 +1,14 @@
"""The command_line component utils."""
from __future__ import annotations
import asyncio
import logging
import subprocess
_LOGGER = logging.getLogger(__name__)
_EXEC_FAILED_CODE = 127
def call_shell_with_timeout(
async def async_call_shell_with_timeout(
command: str, timeout: int, *, log_return_code: bool = True
) -> int:
"""Run a shell command with a timeout.
@ -17,46 +18,45 @@ def call_shell_with_timeout(
"""
try:
_LOGGER.debug("Running command: %s", command)
subprocess.check_output(
proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design
command,
shell=True, # noqa: S602 # shell by design
timeout=timeout,
close_fds=False, # required for posix_spawn
)
return 0
except subprocess.CalledProcessError as proc_exception:
if log_return_code:
async with asyncio.timeout(timeout):
await proc.communicate()
return_code = proc.returncode
if return_code == _EXEC_FAILED_CODE:
_LOGGER.error("Error trying to exec command: %s", command)
elif log_return_code and return_code != 0:
_LOGGER.error(
"Command failed (with return code %s): %s",
proc_exception.returncode,
proc.returncode,
command,
)
return proc_exception.returncode
except subprocess.TimeoutExpired:
return return_code or 0
except TimeoutError:
_LOGGER.error("Timeout for command: %s", command)
return -1
except subprocess.SubprocessError:
_LOGGER.error("Error trying to exec command: %s", command)
return -1
def check_output_or_log(command: str, timeout: int) -> str | None:
async def async_check_output_or_log(command: str, timeout: int) -> str | None:
"""Run a shell command with a timeout and return the output."""
try:
return_value = subprocess.check_output(
proc = await asyncio.create_subprocess_shell( # noqa: S602 # shell by design
command,
shell=True, # noqa: S602 # shell by design
timeout=timeout,
close_fds=False, # required for posix_spawn
stdout=asyncio.subprocess.PIPE,
)
return return_value.strip().decode("utf-8")
except subprocess.CalledProcessError as err:
_LOGGER.error(
"Command failed (with return code %s): %s", err.returncode, command
)
except subprocess.TimeoutExpired:
async with asyncio.timeout(timeout):
stdout, _ = await proc.communicate()
if proc.returncode != 0:
_LOGGER.error(
"Command failed (with return code %s): %s", proc.returncode, command
)
else:
return stdout.strip().decode("utf-8")
except TimeoutError:
_LOGGER.error("Timeout for command: %s", command)
except subprocess.SubprocessError:
_LOGGER.error("Error trying to exec command: %s", command)
return None