Use wait_shared_future in singleton helper (#183889)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
epenet
2026-10-01 10:00:38 -05:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 5153c2a3c2
commit da2a753a0b
2 changed files with 31 additions and 1 deletions
+2 -1
View File
@@ -7,6 +7,7 @@ import inspect
from typing import Any, Literal, assert_type, cast, overload
from homeassistant.core import HomeAssistant
from homeassistant.util.async_ import wait_shared_future
from homeassistant.util.hass_dict import HassKey
type _FuncType[_T] = Callable[[HomeAssistant], _T]
@@ -78,7 +79,7 @@ def singleton[_S, _T, _U](
obj_or_future = hass.data[data_key]
if isinstance(obj_or_future, asyncio.Future):
return cast(_T, await obj_or_future)
return cast(_T, await wait_shared_future(obj_or_future))
return cast(_T, obj_or_future)
+29
View File
@@ -101,3 +101,32 @@ async def test_singleton_async_concurrent_raises(mock_hass: HomeAssistant) -> No
# Only the first caller ran the wrapped function; the waiter observed its error.
assert calls == 1
assert "test_key" not in mock_hass.data
async def test_singleton_async_concurrent_waiter_cancelled(
mock_hass: HomeAssistant,
) -> None:
"""Verify cancelling a waiting caller does not break the in-progress call."""
release = asyncio.Event()
result = object()
@singleton.singleton("test_key")
async def something(hass: HomeAssistant) -> Any:
await release.wait()
return result
task1 = asyncio.create_task(something(mock_hass))
await asyncio.sleep(0)
task2 = asyncio.create_task(something(mock_hass))
await asyncio.sleep(0)
task2.cancel()
with pytest.raises(asyncio.CancelledError):
await task2
release.set()
async with asyncio.timeout(1):
assert await task1 is result
assert mock_hass.data["test_key"] is result
assert await something(mock_hass) is result