From de9f39745b48f5da89d78dc372982f90fc44c68d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 21 Apr 2022 09:03:05 +0200 Subject: [PATCH] Fix race in _process_recorder_platform (#70339) * Fix race in _process_recorder_platform * Update homeassistant/components/recorder/__init__.py Co-authored-by: J. Nick Koston * Update tests Co-authored-by: J. Nick Koston --- homeassistant/components/recorder/__init__.py | 26 ++++++++++++++++--- tests/components/sensor/test_recorder.py | 26 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 4e3109c7dce..1116b261995 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -341,10 +341,8 @@ async def _process_recorder_platform( hass: HomeAssistant, domain: str, platform: Any ) -> None: """Process a recorder platform.""" - platforms: dict[str, Any] = hass.data[DOMAIN] - platforms[domain] = platform - if hasattr(platform, "exclude_attributes"): - hass.data[EXCLUDE_ATTRIBUTES][domain] = platform.exclude_attributes(hass) + instance: Recorder = hass.data[DATA_INSTANCE] + instance.queue.put(AddRecorderPlatformTask(domain, platform)) @callback @@ -601,6 +599,26 @@ class CommitTask(RecorderTask): instance._commit_event_session_or_retry() +@dataclass +class AddRecorderPlatformTask(RecorderTask): + """Add a recorder platform.""" + + domain: str + platform: Any + commit_before = False + + def run(self, instance: Recorder) -> None: + """Handle the task.""" + hass = instance.hass + domain = self.domain + platform = self.platform + + platforms: dict[str, Any] = hass.data[DOMAIN] + platforms[domain] = platform + if hasattr(self.platform, "exclude_attributes"): + hass.data[EXCLUDE_ATTRIBUTES][domain] = platform.exclude_attributes(hass) + + COMMIT_TASK = CommitTask() KEEP_ALIVE_TASK = KeepAliveTask() diff --git a/tests/components/sensor/test_recorder.py b/tests/components/sensor/test_recorder.py index f9f1c058626..343f2678299 100644 --- a/tests/components/sensor/test_recorder.py +++ b/tests/components/sensor/test_recorder.py @@ -107,6 +107,7 @@ def test_compile_hourly_statistics( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -162,6 +163,7 @@ def test_compile_hourly_statistics_purged_state_changes( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -220,6 +222,7 @@ def test_compile_hourly_statistics_unsupported(hass_recorder, caplog, attributes hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added four, states = record_states(hass, zero, "sensor.test1", attributes) attributes_tmp = dict(attributes) @@ -362,6 +365,8 @@ async def test_compile_hourly_sum_statistics_amount( hass.config.units = units recorder = hass.data[DATA_INSTANCE] await async_setup_component(hass, "sensor", {}) + # Wait for the sensor recorder platform to be added + await hass.async_add_executor_job(hass.data[DATA_INSTANCE].block_till_done) attributes = { "device_class": device_class, "state_class": state_class, @@ -524,6 +529,7 @@ def test_compile_hourly_sum_statistics_amount_reset_every_state_change( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": state_class, @@ -630,6 +636,7 @@ def test_compile_hourly_sum_statistics_amount_invalid_last_reset( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": state_class, @@ -709,6 +716,7 @@ def test_compile_hourly_sum_statistics_nan_inf_state( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": state_class, @@ -907,6 +915,7 @@ def test_compile_hourly_sum_statistics_total_no_reset( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "total", @@ -1001,6 +1010,7 @@ def test_compile_hourly_sum_statistics_total_increasing( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "total_increasing", @@ -1093,6 +1103,7 @@ def test_compile_hourly_sum_statistics_total_increasing_small_dip( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "total_increasing", @@ -1189,6 +1200,7 @@ def test_compile_hourly_energy_statistics_unsupported(hass_recorder, caplog): hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added sns1_attr = { "device_class": "energy", "state_class": "total", @@ -1282,6 +1294,7 @@ def test_compile_hourly_energy_statistics_multiple(hass_recorder, caplog): hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added sns1_attr = {**ENERGY_SENSOR_ATTRIBUTES, "last_reset": None} sns2_attr = {**ENERGY_SENSOR_ATTRIBUTES, "last_reset": None} sns3_attr = { @@ -1474,6 +1487,7 @@ def test_compile_hourly_statistics_unchanged( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -1510,6 +1524,7 @@ def test_compile_hourly_statistics_partially_unavailable(hass_recorder, caplog): hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added four, states = record_states_partially_unavailable( hass, zero, "sensor.test1", TEMPERATURE_SENSOR_ATTRIBUTES ) @@ -1561,6 +1576,7 @@ def test_compile_hourly_statistics_unavailable( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -1601,6 +1617,7 @@ def test_compile_hourly_statistics_fails(hass_recorder, caplog): hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added with patch( "homeassistant.components.sensor.recorder.compile_statistics", side_effect=Exception, @@ -1644,6 +1661,7 @@ def test_list_statistic_ids( """Test listing future statistic ids.""" hass = hass_recorder() setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "last_reset": 0, @@ -1687,6 +1705,7 @@ def test_list_statistic_ids_unsupported(hass_recorder, caplog, _attributes): """Test listing future statistic ids for unsupported sensor.""" hass = hass_recorder() setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = dict(_attributes) hass.states.set("sensor.test1", 0, attributes=attributes) if "last_reset" in attributes: @@ -1722,6 +1741,7 @@ def test_compile_hourly_statistics_changing_units_1( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -1824,6 +1844,7 @@ def test_compile_hourly_statistics_changing_units_2( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -1876,6 +1897,7 @@ def test_compile_hourly_statistics_changing_units_3( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": device_class, "state_class": "measurement", @@ -1973,6 +1995,7 @@ def test_compile_hourly_statistics_changing_device_class_1( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added # Record some states for an initial period, the entity has no device class attributes = { @@ -2076,6 +2099,7 @@ def test_compile_hourly_statistics_changing_device_class_2( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added # Record some states for an initial period, the entity has a device class attributes = { @@ -2182,6 +2206,7 @@ def test_compile_hourly_statistics_changing_statistics( hass = hass_recorder() recorder = hass.data[DATA_INSTANCE] setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes_1 = { "device_class": device_class, "state_class": "measurement", @@ -2307,6 +2332,7 @@ def test_compile_statistics_hourly_daily_monthly_summary( recorder = hass.data[DATA_INSTANCE] recorder._db_supports_row_number = db_supports_row_number setup_component(hass, "sensor", {}) + wait_recording_done(hass) # Wait for the sensor recorder platform to be added attributes = { "device_class": None, "state_class": "measurement",