From c948392ebcb0082a6a53a50dbafccbfd44e12976 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 12 Mar 2024 07:43:43 -1000 Subject: [PATCH] Make Bluetooth active coordinator debouncers run tasks in the background (#113129) --- .../bluetooth/active_update_coordinator.py | 1 + .../bluetooth/active_update_processor.py | 1 + .../test_active_update_coordinator.py | 32 ++++++++++--------- .../bluetooth/test_active_update_processor.py | 31 ++++++++---------- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/bluetooth/active_update_coordinator.py b/homeassistant/components/bluetooth/active_update_coordinator.py index 4673e6adaae..df5701a81a3 100644 --- a/homeassistant/components/bluetooth/active_update_coordinator.py +++ b/homeassistant/components/bluetooth/active_update_coordinator.py @@ -99,6 +99,7 @@ class ActiveBluetoothDataUpdateCoordinator( cooldown=POLL_DEFAULT_COOLDOWN, immediate=POLL_DEFAULT_IMMEDIATE, function=self._async_poll, + background=True, ) else: poll_debouncer.function = self._async_poll diff --git a/homeassistant/components/bluetooth/active_update_processor.py b/homeassistant/components/bluetooth/active_update_processor.py index e028017dd31..be4f6553738 100644 --- a/homeassistant/components/bluetooth/active_update_processor.py +++ b/homeassistant/components/bluetooth/active_update_processor.py @@ -92,6 +92,7 @@ class ActiveBluetoothProcessorCoordinator( cooldown=POLL_DEFAULT_COOLDOWN, immediate=POLL_DEFAULT_IMMEDIATE, function=self._async_poll, + background=True, ) else: poll_debouncer.function = self._async_poll diff --git a/tests/components/bluetooth/test_active_update_coordinator.py b/tests/components/bluetooth/test_active_update_coordinator.py index 995b208ead9..e3178f84336 100644 --- a/tests/components/bluetooth/test_active_update_coordinator.py +++ b/tests/components/bluetooth/test_active_update_coordinator.py @@ -129,7 +129,7 @@ async def test_basic_usage( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data == {"fake": "data"} @@ -175,13 +175,13 @@ async def test_bleak_error_during_polling( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data is None assert coordinator.last_poll_successful is False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} assert coordinator.data == {"fake": "data"} assert coordinator.last_poll_successful is True @@ -228,13 +228,13 @@ async def test_generic_exception_during_polling( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data is None assert coordinator.last_poll_successful is False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} assert coordinator.data == {"fake": "data"} assert coordinator.last_poll_successful is True @@ -280,7 +280,7 @@ async def test_polling_debounce( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # We should only get one poll because of the debounce assert coordinator.data == {"poll_count": 1} @@ -316,7 +316,9 @@ async def test_polling_debounce_with_custom_debouncer( mode=BluetoothScanningMode.ACTIVE, needs_poll_method=_needs_poll, poll_method=_poll_method, - poll_debouncer=Debouncer(hass, _LOGGER, cooldown=0.1, immediate=True), + poll_debouncer=Debouncer( + hass, _LOGGER, cooldown=0.1, immediate=True, background=True + ), ) assert coordinator.available is False # no data yet @@ -327,7 +329,7 @@ async def test_polling_debounce_with_custom_debouncer( inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # We should only get one poll because of the debounce assert coordinator.data == {"poll_count": 1} @@ -371,25 +373,25 @@ async def test_polling_rejecting_the_first_time( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # First poll is rejected, so no data yet assert coordinator.data is None inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # Data is the same so no poll check assert coordinator.data is None inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO_2.rssi} # Data is different so poll is done assert coordinator.data == {"fake": "data"} inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} # Data is different again so poll is done assert coordinator.data == {"fake": "data"} @@ -434,19 +436,19 @@ async def test_no_polling_after_stop_event( assert needs_poll_calls == 0 inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.passive_data == {"rssi": GENERIC_BLUETOOTH_SERVICE_INFO.rssi} assert coordinator.data == {"fake": "data"} assert needs_poll_calls == 1 hass.set_state(CoreState.stopping) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 # Should not generate a poll now inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 cancel() diff --git a/tests/components/bluetooth/test_active_update_processor.py b/tests/components/bluetooth/test_active_update_processor.py index e894ef3dab4..e854233451e 100644 --- a/tests/components/bluetooth/test_active_update_processor.py +++ b/tests/components/bluetooth/test_active_update_processor.py @@ -84,7 +84,7 @@ async def test_basic_usage( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert coordinator.available is True @@ -127,10 +127,7 @@ async def test_poll_can_be_skipped( needs_poll_method=_poll_needed, poll_method=_poll, poll_debouncer=Debouncer( - hass, - _LOGGER, - cooldown=0, - immediate=True, + hass, _LOGGER, cooldown=0, immediate=True, background=True ), ) assert coordinator.available is False # no data yet @@ -142,19 +139,19 @@ async def test_poll_can_be_skipped( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": True}) flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, True) flag = True inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": True}) cancel() @@ -208,7 +205,7 @@ async def test_bleak_error_and_recover( # First poll fails inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, False) assert ( @@ -219,7 +216,7 @@ async def test_bleak_error_and_recover( # Second poll works flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": False}) cancel() @@ -272,13 +269,13 @@ async def test_poll_failure_and_recover( # First poll fails inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": None}, False) # Second poll works flag = False inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": False}) cancel() @@ -329,7 +326,7 @@ async def test_second_poll_needed( # Second poll gets stuck behind first poll inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[1] == call({"testdata": 1}) cancel() @@ -381,7 +378,7 @@ async def test_rate_limit( # Third poll gets stuck behind first poll doesn't get queued inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert async_handle_update.mock_calls[-1] == call({"testdata": 1}) cancel() @@ -425,7 +422,7 @@ async def test_no_polling_after_stop_event( cancel = coordinator.async_start() inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 assert coordinator.available is True @@ -438,12 +435,12 @@ async def test_no_polling_after_stop_event( assert async_handle_update.mock_calls[1] == call({"testdata": 1}) hass.set_state(CoreState.stopping) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 # Should not generate a poll now that CoreState is stopping inject_bluetooth_service_info(hass, GENERIC_BLUETOOTH_SERVICE_INFO_2) - await hass.async_block_till_done() + await hass.async_block_till_done(wait_background_tasks=True) assert needs_poll_calls == 1 cancel()