Add a 60s timeout to shell_command to prevent processes from building up (#38491)
If a process never ended, there was not timeout and they would build up in the background until Home Assistant crashed.
This commit is contained in:
parent
c33f309d5f
commit
dddcb8e299
2 changed files with 39 additions and 2 deletions
|
@ -12,6 +12,8 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
|
|
||||||
DOMAIN = "shell_command"
|
DOMAIN = "shell_command"
|
||||||
|
|
||||||
|
COMMAND_TIMEOUT = 60
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
@ -74,7 +76,22 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
process = await create_process
|
process = await create_process
|
||||||
stdout_data, stderr_data = await process.communicate()
|
try:
|
||||||
|
stdout_data, stderr_data = await asyncio.wait_for(
|
||||||
|
process.communicate(), COMMAND_TIMEOUT
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
_LOGGER.exception(
|
||||||
|
"Timed out running command: `%s`, after: %ss", cmd, COMMAND_TIMEOUT
|
||||||
|
)
|
||||||
|
if process:
|
||||||
|
try:
|
||||||
|
await process.kill()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
del process
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
if stdout_data:
|
if stdout_data:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
|
|
@ -6,7 +6,7 @@ from typing import Tuple
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from homeassistant.components import shell_command
|
from homeassistant.components import shell_command
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import async_setup_component, setup_component
|
||||||
|
|
||||||
from tests.async_mock import Mock, patch
|
from tests.async_mock import Mock, patch
|
||||||
from tests.common import get_test_home_assistant
|
from tests.common import get_test_home_assistant
|
||||||
|
@ -178,3 +178,23 @@ class TestShellCommand(unittest.TestCase):
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
assert mock_output.call_count == 1
|
assert mock_output.call_count == 1
|
||||||
assert test_phrase.encode() + b"\n" == mock_output.call_args_list[0][0][-1]
|
assert test_phrase.encode() + b"\n" == mock_output.call_args_list[0][0][-1]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_do_no_run_forever(hass, caplog):
|
||||||
|
"""Test subprocesses terminate after the timeout."""
|
||||||
|
|
||||||
|
with patch.object(shell_command, "COMMAND_TIMEOUT", 0.001):
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
shell_command.DOMAIN,
|
||||||
|
{shell_command.DOMAIN: {"test_service": "sleep 10000"}},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
shell_command.DOMAIN, "test_service", blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Timed out" in caplog.text
|
||||||
|
assert "sleep 10000" in caplog.text
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue