mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Increase open file descriptor limit on startup
Increase the open file descriptor soft limit to 2048 on startup. This is necessary to prevent issues with file descriptor exhaustion in environments where the soft limit is low (e.g. the defaul of 1024 on Linux and since Home Assistant OS 16.0 in its container environment).
This commit is contained in:
parent
72d1c3cfc8
commit
8a0ee762a6
@ -111,6 +111,7 @@ from .util.async_ import create_eager_task
|
||||
from .util.hass_dict import HassKey
|
||||
from .util.logging import async_activate_log_queue_handler
|
||||
from .util.package import async_get_user_site, is_docker_env, is_virtual_env
|
||||
from .util.resource import set_open_file_descriptor_limit
|
||||
from .util.system_info import is_official_image
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
@ -302,6 +303,8 @@ async def async_setup_hass(
|
||||
|
||||
hass = await create_hass()
|
||||
|
||||
set_open_file_descriptor_limit()
|
||||
|
||||
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
||||
_LOGGER.warning(
|
||||
"Skipping pip installation of required modules. This may cause issues"
|
||||
|
63
homeassistant/util/resource.py
Normal file
63
homeassistant/util/resource.py
Normal file
@ -0,0 +1,63 @@
|
||||
"""Resource management utilities for Home Assistant."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import resource
|
||||
from typing import Final
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Default soft file descriptor limit to set
|
||||
DEFAULT_SOFT_FILE_LIMIT: Final = 2048
|
||||
|
||||
|
||||
def set_open_file_descriptor_limit() -> None:
|
||||
"""Set the maximum open file descriptor soft limit."""
|
||||
# Check environment variable first, then use default
|
||||
soft_limit = int(os.environ.get("SOFT_FILE_LIMIT", DEFAULT_SOFT_FILE_LIMIT))
|
||||
|
||||
try:
|
||||
# Get current limits
|
||||
current_soft, current_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Current file descriptor limits: soft=%d, hard=%d",
|
||||
current_soft,
|
||||
current_hard,
|
||||
)
|
||||
|
||||
# Don't increase if already at or above the desired limit
|
||||
if current_soft >= soft_limit:
|
||||
_LOGGER.debug(
|
||||
"Current soft limit (%d) is already >= desired limit (%d), skipping",
|
||||
current_soft,
|
||||
soft_limit,
|
||||
)
|
||||
return
|
||||
|
||||
# Don't set soft limit higher than hard limit
|
||||
if soft_limit > current_hard:
|
||||
_LOGGER.warning(
|
||||
"Requested soft limit (%d) exceeds hard limit (%d), "
|
||||
"setting to hard limit",
|
||||
soft_limit,
|
||||
current_hard,
|
||||
)
|
||||
soft_limit = current_hard
|
||||
|
||||
# Set the new soft limit
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, current_hard))
|
||||
|
||||
# Verify the change
|
||||
new_soft, new_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
_LOGGER.info(
|
||||
"File descriptor limits updated: soft=%d->%d, hard=%d",
|
||||
current_soft,
|
||||
new_soft,
|
||||
new_hard,
|
||||
)
|
||||
|
||||
except OSError as err:
|
||||
_LOGGER.error("Failed to set file descriptor limit: %s", err)
|
92
tests/util/test_resource.py
Normal file
92
tests/util/test_resource.py
Normal file
@ -0,0 +1,92 @@
|
||||
"""Test the resource utility module."""
|
||||
|
||||
import os
|
||||
import resource
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.util.resource import (
|
||||
DEFAULT_SOFT_FILE_LIMIT,
|
||||
set_open_file_descriptor_limit,
|
||||
)
|
||||
|
||||
|
||||
def test_set_open_file_descriptor_limit_default() -> None:
|
||||
"""Test setting file limit with default value."""
|
||||
original_soft, original_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
with patch("homeassistant.util.resource._LOGGER") as mock_logger:
|
||||
set_open_file_descriptor_limit()
|
||||
|
||||
# Check that we attempted to set the limit
|
||||
new_soft, new_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
# If the original soft limit was already >= DEFAULT_SOFT_FILE_LIMIT,
|
||||
# it should remain unchanged
|
||||
if original_soft >= DEFAULT_SOFT_FILE_LIMIT:
|
||||
assert new_soft == original_soft
|
||||
mock_logger.debug.assert_called()
|
||||
else:
|
||||
# Should have been increased to DEFAULT_SOFT_FILE_LIMIT or hard limit
|
||||
expected_soft = min(DEFAULT_SOFT_FILE_LIMIT, original_hard)
|
||||
assert new_soft == expected_soft
|
||||
mock_logger.info.assert_called()
|
||||
|
||||
|
||||
def test_set_open_file_descriptor_limit_environment_variable() -> None:
|
||||
"""Test setting file limit from environment variable."""
|
||||
custom_limit = 1500
|
||||
|
||||
with (
|
||||
patch.dict(os.environ, {"SOFT_FILE_LIMIT": str(custom_limit)}),
|
||||
patch("homeassistant.util.resource._LOGGER") as mock_logger,
|
||||
):
|
||||
original_soft, original_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
set_open_file_descriptor_limit()
|
||||
|
||||
new_soft, new_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
if original_soft >= custom_limit:
|
||||
assert new_soft == original_soft
|
||||
mock_logger.debug.assert_called()
|
||||
else:
|
||||
expected_soft = min(custom_limit, original_hard)
|
||||
assert new_soft == expected_soft
|
||||
|
||||
|
||||
def test_set_open_file_descriptor_limit_exceeds_hard_limit() -> None:
|
||||
"""Test setting file limit that exceeds hard limit."""
|
||||
original_soft, original_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
excessive_limit = original_hard + 1000
|
||||
|
||||
with (
|
||||
patch.dict(os.environ, {"SOFT_FILE_LIMIT": str(excessive_limit)}),
|
||||
patch("homeassistant.util.resource._LOGGER") as mock_logger,
|
||||
):
|
||||
set_open_file_descriptor_limit()
|
||||
|
||||
new_soft, new_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||
|
||||
# Should be capped at hard limit
|
||||
assert new_soft == original_hard
|
||||
mock_logger.warning.assert_called_once()
|
||||
|
||||
|
||||
def test_set_open_file_descriptor_limit_os_error() -> None:
|
||||
"""Test handling OSError when setting file limit."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.util.resource.resource.getrlimit", return_value=(1000, 4096)
|
||||
),
|
||||
patch(
|
||||
"homeassistant.util.resource.resource.setrlimit",
|
||||
side_effect=OSError("Permission denied"),
|
||||
),
|
||||
patch("homeassistant.util.resource._LOGGER") as mock_logger,
|
||||
):
|
||||
set_open_file_descriptor_limit()
|
||||
|
||||
mock_logger.error.assert_called_once()
|
||||
assert (
|
||||
"Failed to set file descriptor limit" in mock_logger.error.call_args[0][0]
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user