From d1b0ab736d31468795649662f28cb986866bcbcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 12 Feb 2020 03:54:19 +0200 Subject: [PATCH] Improve Huawei LTE timeouting/stalling request behavior (#31710) * Suppress data get timeout exceptions for 30s after notify attempts At least SMS send failures may put the API in a state where it times out some (but not all) data get operations for some time, e.g. 25s. Closes https://github.com/home-assistant/home-assistant/issues/30827 * Do not pile up duplicate data requests Do not add another request for a piece of data for which a previous request is still in progress. For example failing SMS sends are known to stall some (but not all) requests for some time, and firing up more is not going to help. --- .../components/huawei_lte/__init__.py | 22 +++++++++++++++++++ homeassistant/components/huawei_lte/const.py | 1 + homeassistant/components/huawei_lte/notify.py | 3 +++ 3 files changed, 26 insertions(+) diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 1d54f972907..d3b2d5b1abd 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -5,6 +5,7 @@ from datetime import timedelta from functools import partial import ipaddress import logging +import time from typing import Any, Callable, Dict, List, Set, Tuple from urllib.parse import urlparse @@ -65,6 +66,7 @@ from .const import ( KEY_MONITORING_STATUS, KEY_MONITORING_TRAFFIC_STATISTICS, KEY_WLAN_HOST_LIST, + NOTIFY_SUPPRESS_TIMEOUT, SERVICE_CLEAR_TRAFFIC_STATISTICS, SERVICE_REBOOT, SERVICE_RESUME_INTEGRATION, @@ -138,9 +140,11 @@ class Router: init=False, factory=lambda: defaultdict(set, ((x, {"initial_scan"}) for x in ALL_KEYS)), ) + inflight_gets: Set[str] = attr.ib(init=False, factory=set) unload_handlers: List[CALLBACK_TYPE] = attr.ib(init=False, factory=list) client: Client suspended = attr.ib(init=False, default=False) + notify_last_attempt: float = attr.ib(init=False, default=-1) def __attrs_post_init__(self): """Set up internal state on init.""" @@ -167,6 +171,10 @@ class Router: def _get_data(self, key: str, func: Callable[[None], Any]) -> None: if not self.subscriptions.get(key): return + if key in self.inflight_gets: + _LOGGER.debug("Skipping already inflight get for %s", key) + return + self.inflight_gets.add(key) _LOGGER.debug("Getting %s for subscribers %s", key, self.subscriptions[key]) try: self.data[key] = func() @@ -189,7 +197,21 @@ class Router: "%s requires authorization, excluding from future updates", key ) self.subscriptions.pop(key) + except Timeout: + grace_left = ( + self.notify_last_attempt - time.monotonic() + NOTIFY_SUPPRESS_TIMEOUT + ) + if grace_left > 0: + _LOGGER.debug( + "%s timed out, %.1fs notify timeout suppress grace remaining", + key, + grace_left, + exc_info=True, + ) + else: + raise finally: + self.inflight_gets.discard(key) _LOGGER.debug("%s=%s", key, self.data.get(key)) def update(self) -> None: diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index c6837fce06c..e227f06cf28 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -12,6 +12,7 @@ UNIT_BYTES = "B" UNIT_SECONDS = "s" CONNECTION_TIMEOUT = 10 +NOTIFY_SUPPRESS_TIMEOUT = 30 SERVICE_CLEAR_TRAFFIC_STATISTICS = "clear_traffic_statistics" SERVICE_REBOOT = "reboot" diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 5619a5d702c..91cc8864eb0 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -1,6 +1,7 @@ """Support for Huawei LTE router notifications.""" import logging +import time from typing import Any, List import attr @@ -57,3 +58,5 @@ class HuaweiLteSmsNotificationService(BaseNotificationService): _LOGGER.debug("Sent to %s: %s", targets, resp) except ResponseErrorException as ex: _LOGGER.error("Could not send to %s: %s", targets, ex) + finally: + self.router.notify_last_attempt = time.monotonic()