fix: cancel both listeners independently on unload (NQ-80-001)
This commit is contained in:
@@ -423,11 +423,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
):
|
):
|
||||||
if remove_listener is None:
|
if remove_listener is None:
|
||||||
continue
|
continue
|
||||||
result = remove_listener()
|
try:
|
||||||
if inspect.isawaitable(result):
|
result = remove_listener()
|
||||||
await result
|
if inspect.isawaitable(result):
|
||||||
except BaseException as err:
|
await result
|
||||||
unload_error = err
|
except BaseException as err:
|
||||||
|
# Cancel EVERY listener even when an earlier remover
|
||||||
|
# raises: a skipped remover would leave a daily
|
||||||
|
# callback firing against a closed database after
|
||||||
|
# unload. The first error is retained and re-raised
|
||||||
|
# only after both removers (and the DB close) ran.
|
||||||
|
if unload_error is None:
|
||||||
|
unload_error = err
|
||||||
finally:
|
finally:
|
||||||
database = getattr(runtime_data, "database", None)
|
database = getattr(runtime_data, "database", None)
|
||||||
if database is not None:
|
if database is not None:
|
||||||
|
|||||||
@@ -241,6 +241,33 @@ async def test_unload_closes_database_even_when_listener_removal_raises() -> Non
|
|||||||
assert database.connected is False, "database leaked despite remover error"
|
assert database.connected is False, "database leaked despite remover error"
|
||||||
assert entry.runtime_data is None
|
assert entry.runtime_data is None
|
||||||
assert DOMAIN not in hass.data
|
assert DOMAIN not in hass.data
|
||||||
|
assert hass.time_change.size == 0, "time-change listener leaked"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_cancels_time_listener_even_when_update_remover_raises() -> None:
|
||||||
|
"""A raising update-listener remover must not skip the time-change cancel.
|
||||||
|
|
||||||
|
Both removers are independent: the update-listener remover raising must
|
||||||
|
still cancel the daily time-change callback, whose action would otherwise
|
||||||
|
fire after unload against a closed database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class _RemoveBoom(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _remover():
|
||||||
|
raise _RemoveBoom()
|
||||||
|
|
||||||
|
entry = make_config_entry(entry_id="update-boom-time-cancel")
|
||||||
|
entry.add_update_listener = lambda listener: _remover
|
||||||
|
hass, _registry = make_hass()
|
||||||
|
await async_setup_entry(hass, entry)
|
||||||
|
assert hass.time_change.size == 1
|
||||||
|
with pytest.raises(_RemoveBoom):
|
||||||
|
await async_unload_entry(hass, entry)
|
||||||
|
assert hass.time_change.size == 0, "time-change listener not cancelled"
|
||||||
|
assert entry.runtime_data is None
|
||||||
|
assert DOMAIN not in hass.data
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_registers_day_rollover_listener(hass, make_entry) -> None:
|
async def test_setup_registers_day_rollover_listener(hass, make_entry) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user