Limit rainbird aiohttp client session to a single connection (#112146)

Limit rainbird to a single open http connection
pull/112516/head
Allen Porter 2024-03-03 16:54:05 -08:00 committed by Paulus Schoutsen
parent 93ee900cb3
commit 274ab2328e
4 changed files with 47 additions and 6 deletions

View File

@ -11,11 +11,10 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from .const import CONF_SERIAL_NUMBER
from .coordinator import RainbirdData
from .coordinator import RainbirdData, async_create_clientsession
_LOGGER = logging.getLogger(__name__)
@ -36,9 +35,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})
clientsession = async_create_clientsession()
entry.async_on_unload(clientsession.close)
controller = AsyncRainbirdController(
AsyncRainbirdClient(
async_get_clientsession(hass),
clientsession,
entry.data[CONF_HOST],
entry.data[CONF_PASSWORD],
)

View File

@ -20,7 +20,6 @@ from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv, selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from .const import (
@ -30,6 +29,7 @@ from .const import (
DOMAIN,
TIMEOUT_SECONDS,
)
from .coordinator import async_create_clientsession
_LOGGER = logging.getLogger(__name__)
@ -101,9 +101,10 @@ class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
Raises a ConfigFlowError on failure.
"""
clientsession = async_create_clientsession()
controller = AsyncRainbirdController(
AsyncRainbirdClient(
async_get_clientsession(self.hass),
clientsession,
host,
password,
)
@ -124,6 +125,8 @@ class RainbirdConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
f"Error connecting to Rain Bird controller: {str(err)}",
"cannot_connect",
) from err
finally:
await clientsession.close()
async def async_finish(
self,

View File

@ -9,6 +9,7 @@ from functools import cached_property
import logging
from typing import TypeVar
import aiohttp
from pyrainbird.async_client import (
AsyncRainbirdController,
RainbirdApiException,
@ -28,6 +29,9 @@ UPDATE_INTERVAL = datetime.timedelta(minutes=1)
# changes, so we refresh it less often.
CALENDAR_UPDATE_INTERVAL = datetime.timedelta(minutes=15)
# Rainbird devices can only accept a single request at a time
CONECTION_LIMIT = 1
_LOGGER = logging.getLogger(__name__)
_T = TypeVar("_T")
@ -43,6 +47,13 @@ class RainbirdDeviceState:
rain_delay: int
def async_create_clientsession() -> aiohttp.ClientSession:
"""Create a rainbird async_create_clientsession with a connection limit."""
return aiohttp.ClientSession(
connector=aiohttp.TCPConnector(limit=CONECTION_LIMIT),
)
class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
"""Coordinator for rainbird API calls."""

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Generator
from http import HTTPStatus
import json
from typing import Any
@ -15,7 +16,7 @@ from homeassistant.components.rainbird.const import (
ATTR_DURATION,
DEFAULT_TRIGGER_TIME_MINUTES,
)
from homeassistant.const import Platform
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, Platform
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@ -155,6 +156,31 @@ def setup_platforms(
yield
@pytest.fixture(autouse=True)
def aioclient_mock(hass: HomeAssistant) -> Generator[AiohttpClientMocker, None, None]:
"""Context manager to mock aiohttp client."""
mocker = AiohttpClientMocker()
def create_session():
session = mocker.create_session(hass.loop)
async def close_session(event):
"""Close session."""
await session.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, close_session)
return session
with patch(
"homeassistant.components.rainbird.async_create_clientsession",
side_effect=create_session,
), patch(
"homeassistant.components.rainbird.config_flow.async_create_clientsession",
side_effect=create_session,
):
yield mocker
def rainbird_json_response(result: dict[str, str]) -> bytes:
"""Create a fake API response."""
return encryption.encrypt(