Log directory blocking I/O functions that run in the event loop (#118529)

* Log directory I/O functions that run in the event loop

* tests
This commit is contained in:
J. Nick Koston 2024-05-31 10:09:19 -05:00 committed by GitHub
parent 1fef4fa1f6
commit 6656f7d6b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 0 deletions

View file

@ -2,8 +2,10 @@
import builtins import builtins
from contextlib import suppress from contextlib import suppress
import glob
from http.client import HTTPConnection from http.client import HTTPConnection
import importlib import importlib
import os
import sys import sys
import threading import threading
import time import time
@ -59,8 +61,22 @@ def enable() -> None:
loop_thread_id=loop_thread_id, loop_thread_id=loop_thread_id,
) )
glob.glob = protect_loop(
glob.glob, strict_core=False, strict=False, loop_thread_id=loop_thread_id
)
glob.iglob = protect_loop(
glob.iglob, strict_core=False, strict=False, loop_thread_id=loop_thread_id
)
if not _IN_TESTS: if not _IN_TESTS:
# Prevent files being opened inside the event loop # Prevent files being opened inside the event loop
os.listdir = protect_loop( # type: ignore[assignment]
os.listdir, strict_core=False, strict=False, loop_thread_id=loop_thread_id
)
os.scandir = protect_loop( # type: ignore[assignment]
os.scandir, strict_core=False, strict=False, loop_thread_id=loop_thread_id
)
builtins.open = protect_loop( # type: ignore[assignment] builtins.open = protect_loop( # type: ignore[assignment]
builtins.open, builtins.open,
strict_core=False, strict_core=False,

View file

@ -1,7 +1,9 @@
"""Tests for async util methods from Python source.""" """Tests for async util methods from Python source."""
import contextlib import contextlib
import glob
import importlib import importlib
import os
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
import time import time
from typing import Any from typing import Any
@ -10,6 +12,7 @@ from unittest.mock import Mock, patch
import pytest import pytest
from homeassistant import block_async_io from homeassistant import block_async_io
from homeassistant.core import HomeAssistant
from tests.common import extract_stack_to_frame from tests.common import extract_stack_to_frame
@ -235,3 +238,55 @@ async def test_protect_open_path(path: Any, caplog: pytest.LogCaptureFixture) ->
open(path).close() open(path).close()
assert "Detected blocking call to open with args" in caplog.text assert "Detected blocking call to open with args" in caplog.text
async def test_protect_loop_glob(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test glob calls in the loop are logged."""
block_async_io.enable()
glob.glob("/dev/null")
assert "Detected blocking call to glob with args" in caplog.text
caplog.clear()
await hass.async_add_executor_job(glob.glob, "/dev/null")
assert "Detected blocking call to glob with args" not in caplog.text
async def test_protect_loop_iglob(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test iglob calls in the loop are logged."""
block_async_io.enable()
glob.iglob("/dev/null")
assert "Detected blocking call to iglob with args" in caplog.text
caplog.clear()
await hass.async_add_executor_job(glob.iglob, "/dev/null")
assert "Detected blocking call to iglob with args" not in caplog.text
async def test_protect_loop_scandir(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test glob calls in the loop are logged."""
block_async_io.enable()
with contextlib.suppress(FileNotFoundError):
os.scandir("/path/that/does/not/exists")
assert "Detected blocking call to scandir with args" in caplog.text
caplog.clear()
with contextlib.suppress(FileNotFoundError):
await hass.async_add_executor_job(os.scandir, "/path/that/does/not/exists")
assert "Detected blocking call to listdir with args" not in caplog.text
async def test_protect_loop_listdir(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test listdir calls in the loop are logged."""
block_async_io.enable()
with contextlib.suppress(FileNotFoundError):
os.listdir("/path/that/does/not/exists")
assert "Detected blocking call to listdir with args" in caplog.text
caplog.clear()
with contextlib.suppress(FileNotFoundError):
await hass.async_add_executor_job(os.listdir, "/path/that/does/not/exists")
assert "Detected blocking call to listdir with args" not in caplog.text