Add startup timeout to bluetooth (#75848)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
J. Nick Koston
2022-07-28 11:14:13 -10:00
committed by Franck Nijhof
parent 53870dd0bc
commit e4e36b51b6
2 changed files with 38 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
"""The bluetooth integration.""" """The bluetooth integration."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
@@ -8,6 +9,7 @@ from enum import Enum
import logging import logging
from typing import Final, Union from typing import Final, Union
import async_timeout
from bleak import BleakError from bleak import BleakError
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData from bleak.backends.scanner import AdvertisementData
@@ -43,6 +45,7 @@ _LOGGER = logging.getLogger(__name__)
UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5 UNAVAILABLE_TRACK_SECONDS: Final = 60 * 5
START_TIMEOUT = 15
SOURCE_LOCAL: Final = "local" SOURCE_LOCAL: Final = "local"
@@ -300,7 +303,13 @@ class BluetoothManager:
self._device_detected, {} self._device_detected, {}
) )
try: try:
async with async_timeout.timeout(START_TIMEOUT):
await self.scanner.start() await self.scanner.start()
except asyncio.TimeoutError as ex:
self._cancel_device_detected()
raise ConfigEntryNotReady(
f"Timed out starting Bluetooth after {START_TIMEOUT} seconds"
) from ex
except (FileNotFoundError, BleakError) as ex: except (FileNotFoundError, BleakError) as ex:
self._cancel_device_detected() self._cancel_device_detected()
raise ConfigEntryNotReady(f"Failed to start Bluetooth: {ex}") from ex raise ConfigEntryNotReady(f"Failed to start Bluetooth: {ex}") from ex

View File

@@ -1,4 +1,5 @@
"""Tests for the Bluetooth integration.""" """Tests for the Bluetooth integration."""
import asyncio
from datetime import timedelta from datetime import timedelta
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@@ -95,6 +96,33 @@ async def test_setup_and_stop_broken_bluetooth(hass, caplog):
assert len(bluetooth.async_discovered_service_info(hass)) == 0 assert len(bluetooth.async_discovered_service_info(hass)) == 0
async def test_setup_and_stop_broken_bluetooth_hanging(hass, caplog):
"""Test we fail gracefully when bluetooth/dbus is hanging."""
mock_bt = []
async def _mock_hang():
await asyncio.sleep(1)
with patch.object(bluetooth, "START_TIMEOUT", 0), patch(
"homeassistant.components.bluetooth.HaBleakScanner.async_setup"
), patch(
"homeassistant.components.bluetooth.HaBleakScanner.start",
side_effect=_mock_hang,
), patch(
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
):
assert await async_setup_component(
hass, bluetooth.DOMAIN, {bluetooth.DOMAIN: {}}
)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
assert "Timed out starting Bluetooth" in caplog.text
async def test_setup_and_retry_adapter_not_yet_available(hass, caplog): async def test_setup_and_retry_adapter_not_yet_available(hass, caplog):
"""Test we retry if the adapter is not yet available.""" """Test we retry if the adapter is not yet available."""
mock_bt = [] mock_bt = []