Use loader.async_suggest_report_issue in async util (#101516)

This commit is contained in:
Erik Montnemery 2023-10-06 19:57:43 +02:00 committed by GitHub
parent 5d0c8947a1
commit 4e98d39106
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 50 deletions

View file

@ -5,12 +5,15 @@ from asyncio import Future, Semaphore, gather, get_running_loop
from asyncio.events import AbstractEventLoop from asyncio.events import AbstractEventLoop
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
import concurrent.futures import concurrent.futures
from contextlib import suppress
import functools import functools
import logging import logging
import threading import threading
from traceback import extract_stack from traceback import extract_stack
from typing import Any, ParamSpec, TypeVar from typing import Any, ParamSpec, TypeVar
from homeassistant.exceptions import HomeAssistantError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_SHUTDOWN_RUN_CALLBACK_THREADSAFE = "_shutdown_run_callback_threadsafe" _SHUTDOWN_RUN_CALLBACK_THREADSAFE = "_shutdown_run_callback_threadsafe"
@ -82,6 +85,14 @@ def check_loop(
The default advisory message is 'Use `await hass.async_add_executor_job()' The default advisory message is 'Use `await hass.async_add_executor_job()'
Set `advise_msg` to an alternate message if the solution differs. Set `advise_msg` to an alternate message if the solution differs.
""" """
# pylint: disable=import-outside-toplevel
from homeassistant.core import HomeAssistant, async_get_hass
from homeassistant.helpers.frame import (
MissingIntegrationFrame,
get_integration_frame,
)
from homeassistant.loader import async_suggest_report_issue
try: try:
get_running_loop() get_running_loop()
in_loop = True in_loop = True
@ -104,54 +115,47 @@ def check_loop(
# stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender # stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender
return return
for frame in reversed(stack): try:
for path in ("custom_components/", "homeassistant/components/"): integration_frame = get_integration_frame()
try: except MissingIntegrationFrame:
index = frame.filename.index(path) # Did not source from integration? Hard error.
found_frame = frame if found_frame is None:
break raise RuntimeError( # noqa: TRY200
except ValueError: f"Detected blocking call to {func.__name__} inside the event loop. "
continue f"{advise_msg or 'Use `await hass.async_add_executor_job()`'}; "
"This is causing stability issues. Please create a bug report at "
f"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
)
if found_frame is not None: hass: HomeAssistant | None = None
break with suppress(HomeAssistantError):
hass = async_get_hass()
# Did not source from integration? Hard error. report_issue = async_suggest_report_issue(
if found_frame is None: hass,
raise RuntimeError( integration_domain=integration_frame.integration,
f"Detected blocking call to {func.__name__} inside the event loop. " module=integration_frame.module,
f"{advise_msg or 'Use `await hass.async_add_executor_job()`'}; " )
"This is causing stability issues. Please report issue"
)
start = index + len(path)
end = found_frame.filename.index("/", start)
integration = found_frame.filename[start:end]
if path == "custom_components/":
extra = " to the custom integration author"
else:
extra = ""
found_frame = integration_frame.frame
_LOGGER.warning( _LOGGER.warning(
( (
"Detected blocking call to %s inside the event loop. This is causing" "Detected blocking call to %s inside the event loop by %sintegration '%s' "
" stability issues. Please report issue%s for %s doing blocking calls at" "at %s, line %s: %s, please %s"
" %s, line %s: %s"
), ),
func.__name__, func.__name__,
extra, "custom " if integration_frame.custom_integration else "",
integration, integration_frame.integration,
found_frame.filename[index:], integration_frame.relative_filename,
found_frame.lineno, found_frame.lineno,
(found_frame.line or "?").strip(), (found_frame.line or "?").strip(),
report_issue,
) )
if strict: if strict:
raise RuntimeError( raise RuntimeError(
"Blocking calls must be done in the executor or a separate thread;" "Blocking calls must be done in the executor or a separate thread;"
f" {advise_msg or 'Use `await hass.async_add_executor_job()`'}; at" f" {advise_msg or 'Use `await hass.async_add_executor_job()`'}; at"
f" {found_frame.filename[index:]}, line {found_frame.lineno}:" f" {integration_frame.relative_filename}, line {found_frame.lineno}:"
f" {(found_frame.line or '?').strip()}" f" {(found_frame.line or '?').strip()}"
) )

View file

@ -48,7 +48,7 @@ async def test_check_loop_async() -> None:
async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) -> None: async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) -> None:
"""Test check_loop detects and raises when called from event loop from integration context.""" """Test check_loop detects and raises when called from event loop from integration context."""
with pytest.raises(RuntimeError), patch( with pytest.raises(RuntimeError), patch(
"homeassistant.util.async_.extract_stack", "homeassistant.helpers.frame.extract_stack",
return_value=[ return_value=[
Mock( Mock(
filename="/home/paulus/homeassistant/core.py", filename="/home/paulus/homeassistant/core.py",
@ -69,10 +69,10 @@ async def test_check_loop_async_integration(caplog: pytest.LogCaptureFixture) ->
): ):
hasync.check_loop(banned_function) hasync.check_loop(banned_function)
assert ( assert (
"Detected blocking call to banned_function inside the event loop. This is " "Detected blocking call to banned_function inside the event loop by integration"
"causing stability issues. Please report issue for hue doing blocking calls at " " 'hue' at homeassistant/components/hue/light.py, line 23: self.light.is_on, "
"homeassistant/components/hue/light.py, line 23: self.light.is_on" "please create a bug report at https://github.com/home-assistant/core/issues?"
in caplog.text "q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" in caplog.text
) )
@ -81,7 +81,7 @@ async def test_check_loop_async_integration_non_strict(
) -> None: ) -> None:
"""Test check_loop detects when called from event loop from integration context.""" """Test check_loop detects when called from event loop from integration context."""
with patch( with patch(
"homeassistant.util.async_.extract_stack", "homeassistant.helpers.frame.extract_stack",
return_value=[ return_value=[
Mock( Mock(
filename="/home/paulus/homeassistant/core.py", filename="/home/paulus/homeassistant/core.py",
@ -102,17 +102,17 @@ async def test_check_loop_async_integration_non_strict(
): ):
hasync.check_loop(banned_function, strict=False) hasync.check_loop(banned_function, strict=False)
assert ( assert (
"Detected blocking call to banned_function inside the event loop. This is " "Detected blocking call to banned_function inside the event loop by integration"
"causing stability issues. Please report issue for hue doing blocking calls at " " 'hue' at homeassistant/components/hue/light.py, line 23: self.light.is_on, "
"homeassistant/components/hue/light.py, line 23: self.light.is_on" "please create a bug report at https://github.com/home-assistant/core/issues?"
in caplog.text "q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22" in caplog.text
) )
async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None: async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None:
"""Test check_loop detects when called from event loop with custom component context.""" """Test check_loop detects when called from event loop with custom component context."""
with pytest.raises(RuntimeError), patch( with pytest.raises(RuntimeError), patch(
"homeassistant.util.async_.extract_stack", "homeassistant.helpers.frame.extract_stack",
return_value=[ return_value=[
Mock( Mock(
filename="/home/paulus/homeassistant/core.py", filename="/home/paulus/homeassistant/core.py",
@ -133,10 +133,10 @@ async def test_check_loop_async_custom(caplog: pytest.LogCaptureFixture) -> None
): ):
hasync.check_loop(banned_function) hasync.check_loop(banned_function)
assert ( assert (
"Detected blocking call to banned_function inside the event loop. This is" "Detected blocking call to banned_function inside the event loop by custom "
" causing stability issues. Please report issue to the custom integration" "integration 'hue' at custom_components/hue/light.py, line 23: self.light.is_on"
" author for hue doing blocking calls at custom_components/hue/light.py, line" ", please create a bug report at https://github.com/home-assistant/core/issues?"
" 23: self.light.is_on" "q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+hue%22"
) in caplog.text ) in caplog.text