Avoid reschedule churn in Storage.async_delay_save (#111091)
* Avoid circular import in Storage.async_delay_save We call Storage.async_delay_save for every entity being added or removed from the registry. The late import took more time than everything else in the function. * Avoid reschedule churn in Storage.async_delay_save When we are adding or removing entities we will call async_delay_save quite often which has to add and remove a TimerHandle on the event loop which can add up when there are a lot of registry items changing. If the timer handle still has 80% of the time remaining on it we will avoid resceduling and let it fire at the time the original async_delay_save call was made. This ensures we do not force the event loop to rebuild its heapq because too many timer handlers were cancelled at once * div0 * add coverage for 0 since we had none * fix bad conflict * tweaks * tweaks * tweaks * tweaks * tweaks * tweaks * more test fixes * mqtt tests rely on event loop overhead
This commit is contained in:
parent
ff0e0b3e77
commit
5b8591ec7e
5 changed files with 149 additions and 18 deletions
|
@ -6,6 +6,7 @@ import os
|
|||
from typing import Any, NamedTuple
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import py
|
||||
import pytest
|
||||
|
||||
|
@ -19,7 +20,11 @@ from homeassistant.helpers import issue_registry as ir, storage
|
|||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.color import RGBColor
|
||||
|
||||
from tests.common import async_fire_time_changed, async_test_home_assistant
|
||||
from tests.common import (
|
||||
async_fire_time_changed,
|
||||
async_fire_time_changed_exact,
|
||||
async_test_home_assistant,
|
||||
)
|
||||
|
||||
MOCK_VERSION = 1
|
||||
MOCK_VERSION_2 = 2
|
||||
|
@ -115,7 +120,7 @@ async def test_loading_parallel(
|
|||
|
||||
|
||||
async def test_saving_with_delay(
|
||||
hass: HomeAssistant, store, hass_storage: dict[str, Any]
|
||||
hass: HomeAssistant, store: storage.Store, hass_storage: dict[str, Any]
|
||||
) -> None:
|
||||
"""Test saving data after a delay."""
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
|
@ -131,6 +136,88 @@ async def test_saving_with_delay(
|
|||
}
|
||||
|
||||
|
||||
async def test_saving_with_delay_churn_reduction(
|
||||
hass: HomeAssistant,
|
||||
store: storage.Store,
|
||||
hass_storage: dict[str, Any],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test saving data after a delay with timer churn reduction."""
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
assert store.key not in hass_storage
|
||||
|
||||
freezer.tick(0.2)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
|
||||
freezer.tick(1)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert hass_storage[store.key] == {
|
||||
"version": MOCK_VERSION,
|
||||
"minor_version": 1,
|
||||
"key": MOCK_KEY,
|
||||
"data": MOCK_DATA,
|
||||
}
|
||||
|
||||
del hass_storage[store.key]
|
||||
# Simulate what some of the registries do when they add 100 entities
|
||||
for _ in range(100):
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
|
||||
freezer.tick(0.2)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
|
||||
freezer.tick(1)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key in hass_storage
|
||||
|
||||
del hass_storage[store.key]
|
||||
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
freezer.tick(0.5)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
freezer.tick(0.8)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
freezer.tick(0.8)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
|
||||
freezer.tick(0.2)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key in hass_storage
|
||||
|
||||
# Make sure if we do another delayed save
|
||||
# and one with a shorter delay, the shorter delay wins
|
||||
del hass_storage[store.key]
|
||||
store.async_delay_save(lambda: MOCK_DATA, 2)
|
||||
freezer.tick(0.2)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key not in hass_storage
|
||||
|
||||
store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
freezer.tick(1.0)
|
||||
async_fire_time_changed_exact(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key in hass_storage
|
||||
|
||||
|
||||
async def test_saving_on_final_write(
|
||||
hass: HomeAssistant, hass_storage: dict[str, Any]
|
||||
) -> None:
|
||||
|
@ -281,6 +368,23 @@ async def test_multiple_delay_save_calls(
|
|||
assert data == {"delay": "no"}
|
||||
|
||||
|
||||
async def test_delay_save_zero(
|
||||
hass: HomeAssistant, store: storage.Store, hass_storage: dict[str, Any]
|
||||
) -> None:
|
||||
"""Test async_delay_save accepts 0."""
|
||||
store.async_delay_save(lambda: {"delay": "0"}, 0)
|
||||
# sleep is to run one event loop to get the task scheduled
|
||||
await asyncio.sleep(0)
|
||||
await hass.async_block_till_done()
|
||||
assert store.key in hass_storage
|
||||
assert hass_storage[store.key] == {
|
||||
"version": MOCK_VERSION,
|
||||
"minor_version": 1,
|
||||
"key": MOCK_KEY,
|
||||
"data": {"delay": "0"},
|
||||
}
|
||||
|
||||
|
||||
async def test_multiple_save_calls(
|
||||
hass: HomeAssistant, store, hass_storage: dict[str, Any]
|
||||
) -> None:
|
||||
|
@ -706,7 +810,7 @@ async def test_os_error_is_fatal(tmpdir: py.path.local) -> None:
|
|||
|
||||
|
||||
async def test_read_only_store(
|
||||
hass: HomeAssistant, read_only_store, hass_storage: dict[str, Any]
|
||||
hass: HomeAssistant, read_only_store: storage.Store, hass_storage: dict[str, Any]
|
||||
) -> None:
|
||||
"""Test store opened in read only mode does not save."""
|
||||
read_only_store.async_delay_save(lambda: MOCK_DATA, 1)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue