Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 3b6e3fe

Browse files
Fix race condition on eheimdigital coordinator setup (home-assistant#138580)
1 parent da9fbf2 commit 3b6e3fe

File tree

5 files changed

+76
-38
lines changed

5 files changed

+76
-38
lines changed

‎homeassistant/components/eheimdigital/coordinator.py‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
from __future__ import annotations
44

5+
import asyncio
56
from collections.abc import Callable
67

78
from aiohttp import ClientError
89
from eheimdigital.device import EheimDigitalDevice
910
from eheimdigital.hub import EheimDigitalHub
10-
from eheimdigital.types import EheimDeviceType
11+
from eheimdigital.types import EheimDeviceType, EheimDigitalClientError
1112

1213
from homeassistant.config_entries import ConfigEntry
1314
from homeassistant.const import CONF_HOST
1415
from homeassistant.core import HomeAssistant
16+
from homeassistant.exceptions import ConfigEntryNotReady
1517
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1618
from homeassistant.helpers.entity_component import DEFAULT_SCAN_INTERVAL
1719
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -43,12 +45,14 @@ def __init__(
4345
name=DOMAIN,
4446
update_interval=DEFAULT_SCAN_INTERVAL,
4547
)
48+
self.main_device_added_event = asyncio.Event()
4649
self.hub = EheimDigitalHub(
4750
host=self.config_entry.data[CONF_HOST],
4851
session=async_get_clientsession(hass),
4952
loop=hass.loop,
5053
receive_callback=self._async_receive_callback,
5154
device_found_callback=self._async_device_found,
55+
main_device_added_event=self.main_device_added_event,
5256
)
5357
self.known_devices: set[str] = set()
5458
self.platform_callbacks: set[AsyncSetupDeviceEntitiesCallback] = set()
@@ -76,8 +80,17 @@ async def _async_receive_callback(self) -> None:
7680
self.async_set_updated_data(self.hub.devices)
7781

7882
async def _async_setup(self) -> None:
79-
await self.hub.connect()
80-
await self.hub.update()
83+
try:
84+
await self.hub.connect()
85+
async with asyncio.timeout(2):
86+
# This event gets triggered when the first message is received from
87+
# the device, it contains the data necessary to create the main device.
88+
# This removes the race condition where the main device is accessed
89+
# before the response from the device is parsed.
90+
await self.main_device_added_event.wait()
91+
await self.hub.update()
92+
except (TimeoutError, EheimDigitalClientError) as err:
93+
raise ConfigEntryNotReady from err
8194

8295
async def _async_update_data(self) -> dict[str, EheimDigitalDevice]:
8396
try:

‎tests/components/eheimdigital/conftest.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from homeassistant.components.eheimdigital.const import DOMAIN
1313
from homeassistant.const import CONF_HOST
14+
from homeassistant.core import HomeAssistant
1415

1516
from tests.common import MockConfigEntry
1617

@@ -79,3 +80,15 @@ def eheimdigital_hub_mock(
7980
}
8081
eheimdigital_hub_mock.return_value.main = classic_led_ctrl_mock
8182
yield eheimdigital_hub_mock
83+
84+
85+
async def init_integration(
86+
hass: HomeAssistant, mock_config_entry: MockConfigEntry
87+
) -> None:
88+
"""Initialize the integration."""
89+
90+
mock_config_entry.add_to_hass(hass)
91+
with patch(
92+
"homeassistant.components.eheimdigital.coordinator.asyncio.Event", new=AsyncMock
93+
):
94+
await hass.config_entries.async_setup(mock_config_entry.entry_id)

‎tests/components/eheimdigital/test_climate.py‎

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Tests for the climate module."""
22

3-
from unittest.mock import MagicMock, patch
3+
from unittest.mock import AsyncMock, MagicMock, patch
44

55
from eheimdigital.types import (
66
EheimDeviceType,
@@ -31,6 +31,8 @@
3131
from homeassistant.exceptions import HomeAssistantError
3232
from homeassistant.helpers import entity_registry as er
3333

34+
from .conftest import init_integration
35+
3436
from tests.common import MockConfigEntry, snapshot_platform
3537

3638

@@ -45,7 +47,13 @@ async def test_setup_heater(
4547
"""Test climate platform setup for heater."""
4648
mock_config_entry.add_to_hass(hass)
4749

48-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.CLIMATE]):
50+
with (
51+
patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.CLIMATE]),
52+
patch(
53+
"homeassistant.components.eheimdigital.coordinator.asyncio.Event",
54+
new=AsyncMock,
55+
),
56+
):
4957
await hass.config_entries.async_setup(mock_config_entry.entry_id)
5058

5159
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
@@ -69,7 +77,13 @@ async def test_dynamic_new_devices(
6977

7078
eheimdigital_hub_mock.return_value.devices = {}
7179

72-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.CLIMATE]):
80+
with (
81+
patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.CLIMATE]),
82+
patch(
83+
"homeassistant.components.eheimdigital.coordinator.asyncio.Event",
84+
new=AsyncMock,
85+
),
86+
):
7387
await hass.config_entries.async_setup(mock_config_entry.entry_id)
7488

7589
assert (
@@ -108,9 +122,7 @@ async def test_set_preset_mode(
108122
heater_mode: HeaterMode,
109123
) -> None:
110124
"""Test setting a preset mode."""
111-
mock_config_entry.add_to_hass(hass)
112-
113-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
125+
await init_integration(hass, mock_config_entry)
114126

115127
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
116128
"00:00:00:00:00:02", EheimDeviceType.VERSION_EHEIM_EXT_HEATER
@@ -146,9 +158,7 @@ async def test_set_temperature(
146158
mock_config_entry: MockConfigEntry,
147159
) -> None:
148160
"""Test setting a preset mode."""
149-
mock_config_entry.add_to_hass(hass)
150-
151-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
161+
await init_integration(hass, mock_config_entry)
152162

153163
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
154164
"00:00:00:00:00:02", EheimDeviceType.VERSION_EHEIM_EXT_HEATER
@@ -189,9 +199,7 @@ async def test_set_hvac_mode(
189199
active: bool,
190200
) -> None:
191201
"""Test setting a preset mode."""
192-
mock_config_entry.add_to_hass(hass)
193-
194-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
202+
await init_integration(hass, mock_config_entry)
195203

196204
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
197205
"00:00:00:00:00:02", EheimDeviceType.VERSION_EHEIM_EXT_HEATER
@@ -231,9 +239,8 @@ async def test_state_update(
231239
heater_mock.is_heating = False
232240
heater_mock.operation_mode = HeaterMode.BIO
233241

234-
mock_config_entry.add_to_hass(hass)
242+
awaitinit_integration(hass, mock_config_entry)
235243

236-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
237244
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
238245
"00:00:00:00:00:02", EheimDeviceType.VERSION_EHEIM_EXT_HEATER
239246
)

‎tests/components/eheimdigital/test_init.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from homeassistant.helpers import device_registry as dr
99
from homeassistant.setup import async_setup_component
1010

11+
from .conftest import init_integration
12+
1113
from tests.common import MockConfigEntry
1214
from tests.typing import WebSocketGenerator
1315

@@ -21,9 +23,8 @@ async def test_remove_device(
2123
) -> None:
2224
"""Test removing a device."""
2325
assert await async_setup_component(hass, "config", {})
24-
mock_config_entry.add_to_hass(hass)
2526

26-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
27+
await init_integration(hass, mock_config_entry)
2728

2829
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
2930
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E

‎tests/components/eheimdigital/test_light.py‎

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for the light module."""
22

33
from datetime import timedelta
4-
from unittest.mock import MagicMock, patch
4+
from unittest.mock import AsyncMock, MagicMock, patch
55

66
from aiohttp import ClientError
77
from eheimdigital.types import EheimDeviceType, LightMode
@@ -26,6 +26,8 @@
2626
from homeassistant.helpers import entity_registry as er
2727
from homeassistant.util.color import value_to_brightness
2828

29+
from .conftest import init_integration
30+
2931
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
3032

3133

@@ -51,7 +53,13 @@ async def test_setup_classic_led_ctrl(
5153

5254
classic_led_ctrl_mock.tankconfig = tankconfig
5355

54-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
56+
with (
57+
patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]),
58+
patch(
59+
"homeassistant.components.eheimdigital.coordinator.asyncio.Event",
60+
new=AsyncMock,
61+
),
62+
):
5563
await hass.config_entries.async_setup(mock_config_entry.entry_id)
5664

5765
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
@@ -75,7 +83,13 @@ async def test_dynamic_new_devices(
7583

7684
eheimdigital_hub_mock.return_value.devices = {}
7785

78-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
86+
with (
87+
patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]),
88+
patch(
89+
"homeassistant.components.eheimdigital.coordinator.asyncio.Event",
90+
new=AsyncMock,
91+
),
92+
):
7993
await hass.config_entries.async_setup(mock_config_entry.entry_id)
8094

8195
assert (
@@ -106,10 +120,8 @@ async def test_turn_off(
106120
classic_led_ctrl_mock: MagicMock,
107121
) -> None:
108122
"""Test turning off the light."""
109-
mock_config_entry.add_to_hass(hass)
123+
awaitinit_integration(hass, mock_config_entry)
110124

111-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
112-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
113125
await mock_config_entry.runtime_data._async_device_found(
114126
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E
115127
)
@@ -143,10 +155,8 @@ async def test_turn_on_brightness(
143155
expected_dim_value: int,
144156
) -> None:
145157
"""Test turning on the light with different brightness values."""
146-
mock_config_entry.add_to_hass(hass)
158+
awaitinit_integration(hass, mock_config_entry)
147159

148-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
149-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
150160
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
151161
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E
152162
)
@@ -173,12 +183,10 @@ async def test_turn_on_effect(
173183
classic_led_ctrl_mock: MagicMock,
174184
) -> None:
175185
"""Test turning on the light with an effect value."""
176-
mock_config_entry.add_to_hass(hass)
177-
178186
classic_led_ctrl_mock.light_mode = LightMode.MAN_MODE
179187

180-
withpatch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
181-
awaithass.config_entries.async_setup(mock_config_entry.entry_id)
188+
awaitinit_integration(hass, mock_config_entry)
189+
182190
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
183191
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E
184192
)
@@ -204,10 +212,8 @@ async def test_state_update(
204212
classic_led_ctrl_mock: MagicMock,
205213
) -> None:
206214
"""Test the light state update."""
207-
mock_config_entry.add_to_hass(hass)
215+
awaitinit_integration(hass, mock_config_entry)
208216

209-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
210-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
211217
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
212218
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E
213219
)
@@ -228,10 +234,8 @@ async def test_update_failed(
228234
freezer: FrozenDateTimeFactory,
229235
) -> None:
230236
"""Test an failed update."""
231-
mock_config_entry.add_to_hass(hass)
237+
awaitinit_integration(hass, mock_config_entry)
232238

233-
with patch("homeassistant.components.eheimdigital.PLATFORMS", [Platform.LIGHT]):
234-
await hass.config_entries.async_setup(mock_config_entry.entry_id)
235239
await eheimdigital_hub_mock.call_args.kwargs["device_found_callback"](
236240
"00:00:00:00:00:01", EheimDeviceType.VERSION_EHEIM_CLASSIC_LED_CTRL_PLUS_E
237241
)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /