From bba225abc5be306cd9613fab8994848b0b4d68a4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 22 Apr 2023 18:14:22 -0500 Subject: [PATCH] Mark onvif events as stale when the subscription renewal fails (#91567) --- homeassistant/components/onvif/event.py | 54 +++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index f4c113dc398edc..ef822dba974a85 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -211,6 +211,12 @@ def async_webhook_working(self) -> None: LOGGER.debug("%s: Switching to webhook for events", self.name) self.pullpoint_manager.async_pause() + @callback + def async_mark_events_stale(self) -> None: + """Mark all events as stale when the subscriptions fail since we are out of sync.""" + self._events.clear() + self.async_callback_listeners() + class PullPointManager: """ONVIF PullPoint Manager. @@ -316,7 +322,13 @@ async def _async_start_pullpoint(self) -> bool: try: try: started = await self._async_create_pullpoint_subscription() - except RemoteProtocolError: + except RequestError: + # + # We should only need to retry on RemoteProtocolError but some cameras + # are flaky and sometimes do not respond to the Renew request so we + # retry on RequestError as well. + # + # For RemoteProtocolError: # http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server # to close the connection at any time, we treat this as a normal and try again # once since we do not want to declare the camera as not supporting PullPoint @@ -433,10 +445,24 @@ async def _async_renew_pullpoint(self) -> bool: # The first time we renew, we may get a Fault error so we # suppress it. The subscription will be restarted in # async_restart later. - await self._pullpoint_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) + try: + await self._pullpoint_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) + except RequestError: + # + # We should only need to retry on RemoteProtocolError but some cameras + # are flaky and sometimes do not respond to the Renew request so we + # retry on RequestError as well. + # + # For RemoteProtocolError: + # http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server + # to close the connection at any time, we treat this as a normal and try again + # once since we do not want to mark events as stale + # if it just happened to close the connection at the wrong time. + await self._pullpoint_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) LOGGER.debug("%s: Renewed PullPoint subscription", self._name) return True except RENEW_ERRORS as err: + self._event_manager.async_mark_events_stale() LOGGER.debug( "%s: Failed to renew PullPoint subscription; %s", self._name, @@ -634,7 +660,13 @@ async def _async_start_webhook(self) -> bool: try: try: await self._async_create_webhook_subscription() - except RemoteProtocolError: + except RequestError: + # + # We should only need to retry on RemoteProtocolError but some cameras + # are flaky and sometimes do not respond to the Renew request so we + # retry on RequestError as well. + # + # For RemoteProtocolError: # http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server # to close the connection at any time, we treat this as a normal and try again # once since we do not want to declare the camera as not supporting webhooks @@ -662,10 +694,24 @@ async def _async_renew_webhook(self) -> bool: if not self._webhook_subscription: return False try: - await self._webhook_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) + try: + await self._webhook_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) + except RequestError: + # + # We should only need to retry on RemoteProtocolError but some cameras + # are flaky and sometimes do not respond to the Renew request so we + # retry on RequestError as well. + # + # For RemoteProtocolError: + # http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server + # to close the connection at any time, we treat this as a normal and try again + # once since we do not want to mark events as stale + # if it just happened to close the connection at the wrong time. + await self._webhook_subscription.Renew(SUBSCRIPTION_RELATIVE_TIME) LOGGER.debug("%s: Renewed Webhook subscription", self._name) return True except RENEW_ERRORS as err: + self._event_manager.async_mark_events_stale() LOGGER.debug( "%s: Failed to renew webhook subscription %s", self._name,