Handle failures during initial Sonos subscription (#73456)

pull/73848/head
jjlawren 2022-06-22 10:56:17 -05:00 committed by GitHub
parent 143e6a7adc
commit 86fde1a644
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 14 deletions

View File

@ -7,6 +7,10 @@ class UnknownMediaType(BrowseError):
"""Unknown media type."""
class SonosSubscriptionsFailed(HomeAssistantError):
"""Subscription creation failed."""
class SonosUpdateError(HomeAssistantError):
"""Update failed."""

View File

@ -57,7 +57,7 @@ from .const import (
SONOS_VANISHED,
SUBSCRIPTION_TIMEOUT,
)
from .exception import S1BatteryMissing, SonosUpdateError
from .exception import S1BatteryMissing, SonosSubscriptionsFailed, SonosUpdateError
from .favorites import SonosFavorites
from .helpers import soco_error
from .media import SonosMedia
@ -324,12 +324,29 @@ class SonosSpeaker:
async with self._subscription_lock:
if self._subscriptions:
return
await self._async_subscribe()
try:
await self._async_subscribe()
except SonosSubscriptionsFailed:
_LOGGER.warning("Creating subscriptions failed for %s", self.zone_name)
await self._async_offline()
async def _async_subscribe(self) -> None:
"""Create event subscriptions."""
_LOGGER.debug("Creating subscriptions for %s", self.zone_name)
subscriptions = [
self._subscribe(getattr(self.soco, service), self.async_dispatch_event)
for service in SUBSCRIPTION_SERVICES
]
results = await asyncio.gather(*subscriptions, return_exceptions=True)
for result in results:
self.log_subscription_result(
result, "Creating subscription", logging.WARNING
)
if any(isinstance(result, Exception) for result in results):
raise SonosSubscriptionsFailed
# Create a polling task in case subscriptions fail or callback events do not arrive
if not self._poll_timer:
self._poll_timer = async_track_time_interval(
@ -342,16 +359,6 @@ class SonosSpeaker:
SCAN_INTERVAL,
)
subscriptions = [
self._subscribe(getattr(self.soco, service), self.async_dispatch_event)
for service in SUBSCRIPTION_SERVICES
]
results = await asyncio.gather(*subscriptions, return_exceptions=True)
for result in results:
self.log_subscription_result(
result, "Creating subscription", logging.WARNING
)
async def _subscribe(
self, target: SubscriptionBase, sub_callback: Callable
) -> None:
@ -585,6 +592,11 @@ class SonosSpeaker:
await self.async_offline()
async def async_offline(self) -> None:
"""Handle removal of speaker when unavailable."""
async with self._subscription_lock:
await self._async_offline()
async def _async_offline(self) -> None:
"""Handle removal of speaker when unavailable."""
if not self.available:
return
@ -602,8 +614,7 @@ class SonosSpeaker:
self._poll_timer()
self._poll_timer = None
async with self._subscription_lock:
await self.async_unsubscribe()
await self.async_unsubscribe()
self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid)

View File

@ -29,3 +29,21 @@ async def test_fallback_to_polling(
assert speaker.subscriptions_failed
assert "falling back to polling" in caplog.text
assert "Activity on Zone A from SonosSpeaker.update_volume" in caplog.text
async def test_subscription_creation_fails(hass: HomeAssistant, async_setup_sonos):
"""Test that subscription creation failures are handled."""
with patch(
"homeassistant.components.sonos.speaker.SonosSpeaker._subscribe",
side_effect=ConnectionError("Took too long"),
):
await async_setup_sonos()
speaker = list(hass.data[DATA_SONOS].discovered.values())[0]
assert not speaker._subscriptions
with patch.object(speaker, "_resub_cooldown_expires_at", None):
speaker.speaker_activity("discovery")
await hass.async_block_till_done()
assert speaker._subscriptions