Construct storage data in the executor to avoid blocking the event loop (#113465)

Construct storage data in the executor

Constructing storage data can be expensive for large files and can block the event
loop. While ideally we optimize the construction of the data, there are some places
we cannot make it any faster. To avoid blocking the loop, the construction of
the data is now done in the executor by running the data_func in the executor.

2024-03-14 11:28:20.178 WARNING (MainThread) [asyncio] Executing <TimerHandle cancelled when=2319925.760294916 Store._async_schedule_callback_delayed_write() created at /Users/bdraco/home-assistant/homeassistant/helpers/storage.py:328> took 0.159 seconds

There is some risk that the data_func is not thread-safe and needs to be run in
the event loop, but I could not find any cases in our existing code where it
would be a problem
This commit is contained in:
J. Nick Koston 2024-03-14 13:53:11 -10:00 committed by GitHub
parent f95d649f44
commit 28836be3eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 8 additions and 5 deletions

View File

@ -374,10 +374,6 @@ class Store(Generic[_T]):
return
data = self._data
if "data_func" in data:
data["data"] = data.pop("data_func")()
self._data = None
if self._read_only:
@ -395,6 +391,9 @@ class Store(Generic[_T]):
"""Write the data."""
os.makedirs(os.path.dirname(path), exist_ok=True)
if "data_func" in data:
data["data"] = data.pop("data_func")()
_LOGGER.debug("Writing data for %s to %s", self.key, path)
json_helper.save_json(
path,

View File

@ -1356,6 +1356,10 @@ def mock_storage(
# To ensure that the data can be serialized
_LOGGER.debug("Writing data to %s: %s", store.key, data_to_write)
raise_contains_mocks(data_to_write)
if "data_func" in data_to_write:
data_to_write["data"] = data_to_write.pop("data_func")()
encoder = store._encoder
if encoder and encoder is not JSONEncoder:
# If they pass a custom encoder that is not the
@ -1363,7 +1367,7 @@ def mock_storage(
dump = ft.partial(json.dumps, cls=store._encoder)
else:
dump = _orjson_default_encoder
data[store.key] = json.loads(dump(data_to_write))
data[store.key] = json_loads(dump(data_to_write))
async def mock_remove(store: storage.Store) -> None:
"""Remove data."""