Refactor state_reported listener setup to avoid merge in async_fire_internal (#117953)

* Refactor state_reported listener setup to avoid merge in async_fire_internal

Instead of merging the listeners in async_fire_internal, setup the listener for
state_changed at the same time so async_fire_internal can avoid having to copy
another list

* Refactor state_reported listener setup to avoid merge in async_fire_internal

Instead of merging the listeners in async_fire_internal, setup the listener for
state_changed at the same time so async_fire_internal can avoid having to copy
another list

* tweak

* tweak

* tweak

* tweak

* tweak
This commit is contained in:
J. Nick Koston 2024-05-22 17:14:50 -10:00 committed by GitHub
parent 1f7245ecf2
commit e663d4f602
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 33 additions and 18 deletions

View File

@ -1500,7 +1500,6 @@ class EventBus:
This method must be run in the event loop. This method must be run in the event loop.
""" """
if self._debug: if self._debug:
_LOGGER.debug( _LOGGER.debug(
"Bus:Handling %s", _event_repr(event_type, origin, event_data) "Bus:Handling %s", _event_repr(event_type, origin, event_data)
@ -1511,17 +1510,9 @@ class EventBus:
match_all_listeners = self._match_all_listeners match_all_listeners = self._match_all_listeners
else: else:
match_all_listeners = EMPTY_LIST match_all_listeners = EMPTY_LIST
if event_type == EVENT_STATE_CHANGED:
aliased_listeners = self._listeners.get(EVENT_STATE_REPORTED, EMPTY_LIST)
else:
aliased_listeners = EMPTY_LIST
listeners = listeners + match_all_listeners + aliased_listeners
if not listeners:
return
event: Event[_DataT] | None = None event: Event[_DataT] | None = None
for job, event_filter in listeners + match_all_listeners:
for job, event_filter in listeners:
if event_filter is not None: if event_filter is not None:
try: try:
if event_data is None or not event_filter(event_data): if event_data is None or not event_filter(event_data):
@ -1599,18 +1590,32 @@ class EventBus:
if event_filter is not None and not is_callback_check_partial(event_filter): if event_filter is not None and not is_callback_check_partial(event_filter):
raise HomeAssistantError(f"Event filter {event_filter} is not a callback") raise HomeAssistantError(f"Event filter {event_filter} is not a callback")
filterable_job = (HassJob(listener, f"listen {event_type}"), event_filter)
if event_type == EVENT_STATE_REPORTED: if event_type == EVENT_STATE_REPORTED:
if not event_filter: if not event_filter:
raise HomeAssistantError( raise HomeAssistantError(
f"Event filter is required for event {event_type}" f"Event filter is required for event {event_type}"
) )
return self._async_listen_filterable_job( # Special case for EVENT_STATE_REPORTED, we also want to listen to
event_type, # EVENT_STATE_CHANGED
( self._listeners[EVENT_STATE_REPORTED].append(filterable_job)
HassJob(listener, f"listen {event_type}"), self._listeners[EVENT_STATE_CHANGED].append(filterable_job)
event_filter, return functools.partial(
), self._async_remove_multiple_listeners,
(EVENT_STATE_REPORTED, EVENT_STATE_CHANGED),
filterable_job,
) )
return self._async_listen_filterable_job(event_type, filterable_job)
@callback
def _async_remove_multiple_listeners(
self,
keys: Iterable[EventType[_DataT] | str],
filterable_job: _FilterableJobType[Any],
) -> None:
"""Remove multiple listeners for specific event_types."""
for key in keys:
self._async_remove_listener(key, filterable_job)
@callback @callback
def _async_listen_filterable_job( def _async_listen_filterable_job(
@ -1618,6 +1623,7 @@ class EventBus:
event_type: EventType[_DataT] | str, event_type: EventType[_DataT] | str,
filterable_job: _FilterableJobType[_DataT], filterable_job: _FilterableJobType[_DataT],
) -> CALLBACK_TYPE: ) -> CALLBACK_TYPE:
"""Listen for all events or events of a specific type."""
self._listeners[event_type].append(filterable_job) self._listeners[event_type].append(filterable_job)
return functools.partial( return functools.partial(
self._async_remove_listener, event_type, filterable_job self._async_remove_listener, event_type, filterable_job

View File

@ -3346,7 +3346,9 @@ async def test_statemachine_report_state(hass: HomeAssistant) -> None:
hass.states.async_set("light.bowl", "on", {}) hass.states.async_set("light.bowl", "on", {})
state_changed_events = async_capture_events(hass, EVENT_STATE_CHANGED) state_changed_events = async_capture_events(hass, EVENT_STATE_CHANGED)
state_reported_events = [] state_reported_events = []
hass.bus.async_listen(EVENT_STATE_REPORTED, listener, event_filter=mock_filter) unsub = hass.bus.async_listen(
EVENT_STATE_REPORTED, listener, event_filter=mock_filter
)
hass.states.async_set("light.bowl", "on") hass.states.async_set("light.bowl", "on")
await hass.async_block_till_done() await hass.async_block_till_done()
@ -3368,6 +3370,13 @@ async def test_statemachine_report_state(hass: HomeAssistant) -> None:
assert len(state_changed_events) == 3 assert len(state_changed_events) == 3
assert len(state_reported_events) == 4 assert len(state_reported_events) == 4
unsub()
hass.states.async_set("light.bowl", "on")
await hass.async_block_till_done()
assert len(state_changed_events) == 4
assert len(state_reported_events) == 4
async def test_report_state_listener_restrictions(hass: HomeAssistant) -> None: async def test_report_state_listener_restrictions(hass: HomeAssistant) -> None:
"""Test we enforce requirements for EVENT_STATE_REPORTED listeners.""" """Test we enforce requirements for EVENT_STATE_REPORTED listeners."""