Reduce overhead to fire events (#95163)

This commit is contained in:
J. Nick Koston 2023-06-24 14:39:13 -05:00 committed by GitHub
parent 9354df975c
commit 5059cee53f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 68 deletions

View file

@ -82,7 +82,7 @@ from .exceptions import (
)
from .helpers.aiohttp_compat import restore_original_aiohttp_cancel_behavior
from .helpers.json import json_dumps
from .util import dt as dt_util, location, ulid as ulid_util
from .util import dt as dt_util, location
from .util.async_ import (
cancelling,
run_callback_threadsafe,
@ -91,6 +91,7 @@ from .util.async_ import (
from .util.json import JsonObjectType
from .util.read_only_dict import ReadOnlyDict
from .util.timeout import TimeoutManager
from .util.ulid import ulid, ulid_at_time
from .util.unit_system import (
_CONF_UNIT_SYSTEM_IMPERIAL,
_CONF_UNIT_SYSTEM_US_CUSTOMARY,
@ -874,7 +875,7 @@ class Context:
id: str | None = None, # pylint: disable=redefined-builtin
) -> None:
"""Init the context."""
self.id = id or ulid_util.ulid()
self.id = id or ulid()
self.user_id = user_id
self.parent_id = parent_id
self.origin_event: Event | None = None
@ -926,10 +927,14 @@ class Event:
self.data = data or {}
self.origin = origin
self.time_fired = time_fired or dt_util.utcnow()
self.context: Context = context or Context(
id=ulid_util.ulid_at_time(dt_util.utc_to_timestamp(self.time_fired))
)
if not context:
context = Context(
id=ulid_at_time(dt_util.utc_to_timestamp(self.time_fired))
)
self.context = context
self._as_dict: ReadOnlyDict[str, Any] | None = None
if not context.origin_event:
context.origin_event = self
def as_dict(self) -> ReadOnlyDict[str, Any]:
"""Create a dict representation of this Event.
@ -973,6 +978,8 @@ class EventBus:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize a new event bus."""
self._listeners: dict[str, list[_FilterableJob]] = {}
self._match_all_listeners: list[_FilterableJob] = []
self._listeners[MATCH_ALL] = self._match_all_listeners
self._hass = hass
@callback
@ -1019,20 +1026,19 @@ class EventBus:
)
listeners = self._listeners.get(event_type, [])
match_all_listeners = self._match_all_listeners
# EVENT_HOMEASSISTANT_CLOSE should go only to this listeners
match_all_listeners = self._listeners.get(MATCH_ALL)
if match_all_listeners is not None and event_type != EVENT_HOMEASSISTANT_CLOSE:
if not listeners and not match_all_listeners:
return
# EVENT_HOMEASSISTANT_CLOSE should not be sent to MATCH_ALL listeners
if event_type != EVENT_HOMEASSISTANT_CLOSE:
listeners = match_all_listeners + listeners
event = Event(event_type, event_data, origin, time_fired, context)
if not event.context.origin_event:
event.context.origin_event = event
_LOGGER.debug("Bus:Handling %s", event)
if not listeners:
return
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Bus:Handling %s", event)
for job, event_filter, run_immediately in listeners:
if event_filter is not None:
@ -1195,7 +1201,7 @@ class EventBus:
self._listeners[event_type].remove(filterable_job)
# delete event_type list if empty
if not self._listeners[event_type]:
if not self._listeners[event_type] and event_type != MATCH_ALL:
self._listeners.pop(event_type)
except (KeyError, ValueError):
# KeyError is key event_type listener did not exist
@ -1630,7 +1636,7 @@ class StateMachine:
# https://github.com/python/cpython/blob/c90a862cdcf55dc1753c6466e5fa4a467a13ae24/Modules/_datetimemodule.c#L6323
timestamp = time.time()
now = dt_util.utc_from_timestamp(timestamp)
context = Context(id=ulid_util.ulid_at_time(timestamp))
context = Context(id=ulid_at_time(timestamp))
else:
now = dt_util.utcnow()

View file

@ -296,6 +296,7 @@ async def test_service_calls_off_mode(
device.set_setpoint_heat.reset_mock()
device.set_setpoint_heat.side_effect = aiosomecomfort.SomeComfortError
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
@ -308,8 +309,9 @@ async def test_service_calls_off_mode(
)
device.set_setpoint_cool.assert_called_with(95)
device.set_setpoint_heat.assert_called_with(77)
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
caplog.clear()
reset_mock(device)
await hass.services.async_call(
CLIMATE_DOMAIN,
@ -436,6 +438,7 @@ async def test_service_calls_cool_mode(
device.set_setpoint_cool.assert_called_with(95)
device.set_setpoint_heat.assert_called_with(77)
caplog.clear()
device.set_setpoint_cool.reset_mock()
device.set_setpoint_cool.side_effect = aiosomecomfort.SomeComfortError
await hass.services.async_call(
@ -450,7 +453,7 @@ async def test_service_calls_cool_mode(
)
device.set_setpoint_cool.assert_called_with(95)
device.set_setpoint_heat.assert_called_with(77)
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
reset_mock(device)
await hass.services.async_call(
@ -467,6 +470,7 @@ async def test_service_calls_cool_mode(
reset_mock(device)
device.set_hold_cool.side_effect = aiosomecomfort.SomeComfortError
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
@ -478,7 +482,7 @@ async def test_service_calls_cool_mode(
device.set_hold_cool.assert_called_once_with(True, 12)
device.set_hold_heat.assert_not_called()
device.set_setpoint_heat.assert_not_called()
assert "Temperature out of range" in caplog.messages[-1]
assert "Temperature out of range" in caplog.text
reset_mock(device)
@ -512,6 +516,7 @@ async def test_service_calls_cool_mode(
device.raw_ui_data["StatusHeat"] = 2
device.raw_ui_data["StatusCool"] = 2
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
@ -521,7 +526,7 @@ async def test_service_calls_cool_mode(
)
device.set_hold_cool.assert_called_once_with(True)
device.set_hold_heat.assert_not_called()
assert "Couldn't set permanent hold" in caplog.messages[-1]
assert "Couldn't set permanent hold" in caplog.text
reset_mock(device)
@ -536,6 +541,7 @@ async def test_service_calls_cool_mode(
device.set_hold_cool.assert_called_once_with(False)
reset_mock(device)
caplog.clear()
device.set_hold_cool.side_effect = aiosomecomfort.SomeComfortError
@ -548,7 +554,7 @@ async def test_service_calls_cool_mode(
device.set_hold_heat.assert_not_called()
device.set_hold_cool.assert_called_once_with(False)
assert "Can not stop hold mode" in caplog.messages[-1]
assert "Can not stop hold mode" in caplog.text
reset_mock(device)
@ -566,6 +572,8 @@ async def test_service_calls_cool_mode(
device.set_hold_heat.assert_not_called()
reset_mock(device)
caplog.clear()
device.set_hold_cool.side_effect = aiosomecomfort.SomeComfortError
device.raw_ui_data["StatusHeat"] = 2
@ -580,9 +588,10 @@ async def test_service_calls_cool_mode(
device.set_hold_cool.assert_called_once_with(True)
device.set_hold_heat.assert_not_called()
assert "Couldn't set permanent hold" in caplog.messages[-1]
assert "Couldn't set permanent hold" in caplog.text
reset_mock(device)
caplog.clear()
device.raw_ui_data["StatusHeat"] = 2
device.raw_ui_data["StatusCool"] = 2
@ -597,7 +606,7 @@ async def test_service_calls_cool_mode(
device.set_hold_cool.assert_not_called()
device.set_hold_heat.assert_not_called()
assert "Invalid system mode returned" in caplog.messages[-2]
assert "Invalid system mode returned" in caplog.text
async def test_service_calls_heat_mode(
@ -638,8 +647,9 @@ async def test_service_calls_heat_mode(
)
device.set_hold_heat.assert_called_once_with(datetime.time(2, 30), 59)
device.set_hold_heat.reset_mock()
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
@ -667,7 +677,7 @@ async def test_service_calls_heat_mode(
)
device.set_setpoint_cool.assert_called_with(95)
device.set_setpoint_heat.assert_called_with(77)
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
reset_mock(device)
device.raw_ui_data["StatusHeat"] = 2
@ -696,6 +706,7 @@ async def test_service_calls_heat_mode(
device.set_setpoint_heat.assert_called_once()
reset_mock(device)
caplog.clear()
device.set_hold_heat.side_effect = aiosomecomfort.SomeComfortError
@ -710,7 +721,7 @@ async def test_service_calls_heat_mode(
)
device.set_hold_heat.assert_called_once_with(True)
device.set_hold_cool.assert_not_called()
assert "Couldn't set permanent hold" in caplog.messages[-1]
assert "Couldn't set permanent hold" in caplog.text
reset_mock(device)
@ -726,6 +737,7 @@ async def test_service_calls_heat_mode(
device.set_setpoint_cool.assert_not_called()
reset_mock(device)
caplog.clear()
device.set_hold_heat.side_effect = aiosomecomfort.SomeComfortError
@ -739,9 +751,10 @@ async def test_service_calls_heat_mode(
device.set_hold_heat.assert_called_once_with(True, 22)
device.set_hold_cool.assert_not_called()
device.set_setpoint_cool.assert_not_called()
assert "Temperature out of range" in caplog.messages[-1]
assert "Temperature out of range" in caplog.text
reset_mock(device)
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
@ -765,7 +778,7 @@ async def test_service_calls_heat_mode(
)
device.set_hold_heat.assert_called_once_with(False)
assert "Can not stop hold mode" in caplog.messages[-1]
assert "Can not stop hold mode" in caplog.text
reset_mock(device)
device.raw_ui_data["StatusHeat"] = 2
@ -844,6 +857,7 @@ async def test_service_calls_auto_mode(
device.set_setpoint_heat.assert_called_once_with(77)
reset_mock(device)
caplog.clear()
device.set_hold_cool.side_effect = aiosomecomfort.SomeComfortError
device.set_hold_heat.side_effect = aiosomecomfort.SomeComfortError
@ -855,9 +869,10 @@ async def test_service_calls_auto_mode(
blocking=True,
)
device.set_setpoint_heat.assert_not_called()
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
reset_mock(device)
caplog.clear()
device.set_setpoint_heat.side_effect = aiosomecomfort.SomeComfortError
device.set_setpoint_cool.side_effect = aiosomecomfort.SomeComfortError
@ -872,9 +887,10 @@ async def test_service_calls_auto_mode(
blocking=True,
)
device.set_setpoint_heat.assert_not_called()
assert "Invalid temperature" in caplog.messages[-1]
assert "Invalid temperature" in caplog.text
reset_mock(device)
caplog.clear()
device.set_hold_heat.side_effect = None
device.set_hold_cool.side_effect = None
@ -893,6 +909,7 @@ async def test_service_calls_auto_mode(
device.set_hold_heat.assert_called_once_with(True)
reset_mock(device)
caplog.clear()
device.set_hold_heat.side_effect = aiosomecomfort.SomeComfortError
device.raw_ui_data["StatusHeat"] = 2
@ -906,7 +923,7 @@ async def test_service_calls_auto_mode(
)
device.set_hold_cool.assert_called_once_with(True)
device.set_hold_heat.assert_called_once_with(True)
assert "Couldn't set permanent hold" in caplog.messages[-1]
assert "Couldn't set permanent hold" in caplog.text
reset_mock(device)
device.set_setpoint_heat.side_effect = None
@ -923,6 +940,7 @@ async def test_service_calls_auto_mode(
device.set_hold_heat.assert_called_once_with(True, 22)
reset_mock(device)
caplog.clear()
await hass.services.async_call(
CLIMATE_DOMAIN,
@ -946,9 +964,10 @@ async def test_service_calls_auto_mode(
device.set_hold_heat.assert_not_called()
device.set_hold_cool.assert_called_once_with(False)
assert "Can not stop hold mode" in caplog.messages[-1]
assert "Can not stop hold mode" in caplog.text
reset_mock(device)
caplog.clear()
device.raw_ui_data["StatusHeat"] = 2
device.raw_ui_data["StatusCool"] = 2
@ -978,7 +997,7 @@ async def test_service_calls_auto_mode(
device.set_hold_cool.assert_called_once_with(True)
device.set_hold_heat.assert_not_called()
assert "Couldn't set permanent hold" in caplog.messages[-1]
assert "Couldn't set permanent hold" in caplog.text
async def test_async_update_errors(

View file

@ -49,7 +49,7 @@ async def test_setup_no_mqtt(
async def test_setup_with_pub(hass: HomeAssistant, mqtt_mock: MqttMockHAClient) -> None:
"""Test the setup with subscription."""
# Should start off with no listeners for all events
assert hass.bus.async_listeners().get("*") is None
assert not hass.bus.async_listeners().get("*")
assert await add_eventstream(hass, pub_topic="bar")
await hass.async_block_till_done()

View file

@ -11,7 +11,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.common import assert_setup_component, async_fire_time_changed
from tests.common import (
assert_setup_component,
async_capture_events,
async_fire_time_changed,
)
_LOGGER = logging.getLogger(__name__)
@ -222,9 +226,9 @@ async def test_start_stop(mock_pilight_error, hass: HomeAssistant) -> None:
@patch("pilight.pilight.Client", PilightDaemonSim)
@patch("homeassistant.core._LOGGER.debug")
async def test_receive_code(mock_debug, hass: HomeAssistant) -> None:
async def test_receive_code(hass: HomeAssistant) -> None:
"""Check if code receiving via pilight daemon works."""
events = async_capture_events(hass, pilight.EVENT)
with assert_setup_component(4):
assert await async_setup_component(hass, pilight.DOMAIN, {pilight.DOMAIN: {}})
@ -239,18 +243,13 @@ async def test_receive_code(mock_debug, hass: HomeAssistant) -> None:
},
**PilightDaemonSim.test_message["message"],
)
debug_log_call = mock_debug.call_args_list[-1]
# Check if all message parts are put on event bus
for key, value in expected_message.items():
assert str(key) in str(debug_log_call)
assert str(value) in str(debug_log_call)
assert events[0].data == expected_message
@patch("pilight.pilight.Client", PilightDaemonSim)
@patch("homeassistant.core._LOGGER.debug")
async def test_whitelist_exact_match(mock_debug, hass: HomeAssistant) -> None:
async def test_whitelist_exact_match(hass: HomeAssistant) -> None:
"""Check whitelist filter with matched data."""
events = async_capture_events(hass, pilight.EVENT)
with assert_setup_component(4):
whitelist = {
"protocol": [PilightDaemonSim.test_message["protocol"]],
@ -272,18 +271,14 @@ async def test_whitelist_exact_match(mock_debug, hass: HomeAssistant) -> None:
},
**PilightDaemonSim.test_message["message"],
)
debug_log_call = mock_debug.call_args_list[-1]
# Check if all message parts are put on event bus
for key, value in expected_message.items():
assert str(key) in str(debug_log_call)
assert str(value) in str(debug_log_call)
assert events[0].data == expected_message
@patch("pilight.pilight.Client", PilightDaemonSim)
@patch("homeassistant.core._LOGGER.debug")
async def test_whitelist_partial_match(mock_debug, hass: HomeAssistant) -> None:
async def test_whitelist_partial_match(hass: HomeAssistant) -> None:
"""Check whitelist filter with partially matched data, should work."""
events = async_capture_events(hass, pilight.EVENT)
with assert_setup_component(4):
whitelist = {
"protocol": [PilightDaemonSim.test_message["protocol"]],
@ -303,18 +298,15 @@ async def test_whitelist_partial_match(mock_debug, hass: HomeAssistant) -> None:
},
**PilightDaemonSim.test_message["message"],
)
debug_log_call = mock_debug.call_args_list[-1]
# Check if all message parts are put on event bus
for key, value in expected_message.items():
assert str(key) in str(debug_log_call)
assert str(value) in str(debug_log_call)
assert events[0].data == expected_message
@patch("pilight.pilight.Client", PilightDaemonSim)
@patch("homeassistant.core._LOGGER.debug")
async def test_whitelist_or_match(mock_debug, hass: HomeAssistant) -> None:
async def test_whitelist_or_match(hass: HomeAssistant) -> None:
"""Check whitelist filter with several subsection, should work."""
events = async_capture_events(hass, pilight.EVENT)
with assert_setup_component(4):
whitelist = {
"protocol": [
@ -337,18 +329,15 @@ async def test_whitelist_or_match(mock_debug, hass: HomeAssistant) -> None:
},
**PilightDaemonSim.test_message["message"],
)
debug_log_call = mock_debug.call_args_list[-1]
# Check if all message parts are put on event bus
for key, value in expected_message.items():
assert str(key) in str(debug_log_call)
assert str(value) in str(debug_log_call)
assert events[0].data == expected_message
@patch("pilight.pilight.Client", PilightDaemonSim)
@patch("homeassistant.core._LOGGER.debug")
async def test_whitelist_no_match(mock_debug, hass: HomeAssistant) -> None:
async def test_whitelist_no_match(hass: HomeAssistant) -> None:
"""Check whitelist filter with unmatched data, should not work."""
events = async_capture_events(hass, pilight.EVENT)
with assert_setup_component(4):
whitelist = {
"protocol": ["wrong_protocol"],
@ -360,9 +349,8 @@ async def test_whitelist_no_match(mock_debug, hass: HomeAssistant) -> None:
await hass.async_start()
await hass.async_block_till_done()
debug_log_call = mock_debug.call_args_list[-1]
assert "Event pilight_received" not in debug_log_call
assert len(events) == 0
async def test_call_rate_delay_throttle_enabled(hass: HomeAssistant) -> None: