diff --git a/homeassistant/util/async_.py b/homeassistant/util/async_.py index 229a8fef366..8f9526b6800 100644 --- a/homeassistant/util/async_.py +++ b/homeassistant/util/async_.py @@ -88,7 +88,7 @@ def run_callback_threadsafe( return future -def check_loop(strict: bool = True) -> None: +def check_loop(func: Callable, strict: bool = True) -> None: """Warn if called inside the event loop. Raise if `strict` is True.""" try: get_running_loop() @@ -101,7 +101,18 @@ def check_loop(strict: bool = True) -> None: found_frame = None - for frame in reversed(extract_stack()): + stack = extract_stack() + + if ( + func.__name__ == "sleep" + and len(stack) >= 3 + and stack[-3].filename.endswith("pydevd.py") + ): + # Don't report `time.sleep` injected by the debugger (pydevd.py) + # stack[-1] is us, stack[-2] is protected_loop_func, stack[-3] is the offender + return + + for frame in reversed(stack): for path in ("custom_components/", "homeassistant/components/"): try: index = frame.filename.index(path) @@ -152,7 +163,7 @@ def protect_loop(func: Callable, strict: bool = True) -> Callable: @functools.wraps(func) def protected_loop_func(*args, **kwargs): # type: ignore - check_loop(strict=strict) + check_loop(func, strict=strict) return func(*args, **kwargs) return protected_loop_func diff --git a/tests/util/test_async.py b/tests/util/test_async.py index d272da8fe96..f02d3c03b4b 100644 --- a/tests/util/test_async.py +++ b/tests/util/test_async.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, Mock, patch import pytest +from homeassistant import block_async_io from homeassistant.util import async_ as hasync @@ -70,10 +71,14 @@ def test_run_callback_threadsafe_from_inside_event_loop(mock_ident, _): assert len(loop.call_soon_threadsafe.mock_calls) == 2 +def banned_function(): + """Mock banned function.""" + + async def test_check_loop_async(): """Test check_loop detects when called from event loop without integration context.""" with pytest.raises(RuntimeError): - hasync.check_loop() + hasync.check_loop(banned_function) async def test_check_loop_async_integration(caplog): @@ -98,7 +103,7 @@ async def test_check_loop_async_integration(caplog): ), ], ): - hasync.check_loop() + hasync.check_loop(banned_function) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue for hue doing blocking calls at " @@ -129,7 +134,7 @@ async def test_check_loop_async_integration_non_strict(caplog): ), ], ): - hasync.check_loop(strict=False) + hasync.check_loop(banned_function, strict=False) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue for hue doing blocking calls at " @@ -160,7 +165,7 @@ async def test_check_loop_async_custom(caplog): ), ], ): - hasync.check_loop() + hasync.check_loop(banned_function) assert ( "Detected blocking call inside the event loop. This is causing stability issues. " "Please report issue to the custom component author for hue doing blocking calls " @@ -170,7 +175,7 @@ async def test_check_loop_async_custom(caplog): def test_check_loop_sync(caplog): """Test check_loop does nothing when called from thread.""" - hasync.check_loop() + hasync.check_loop(banned_function) assert "Detected blocking call inside the event loop" not in caplog.text @@ -179,10 +184,38 @@ def test_protect_loop_sync(): func = Mock() with patch("homeassistant.util.async_.check_loop") as mock_check_loop: hasync.protect_loop(func)(1, test=2) - mock_check_loop.assert_called_once_with(strict=True) + mock_check_loop.assert_called_once_with(func, strict=True) func.assert_called_once_with(1, test=2) +async def test_protect_loop_debugger_sleep(caplog): + """Test time.sleep injected by the debugger is not reported.""" + block_async_io.enable() + + with patch( + "homeassistant.util.async_.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/homeassistant/.venv/blah/pydevd.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/homeassistant/util/async.py", + lineno="123", + line="protected_loop_func", + ), + Mock( + filename="/home/paulus/homeassistant/util/async.py", + lineno="123", + line="check_loop()", + ), + ], + ): + time.sleep(0) + assert "Detected blocking call inside the event loop" not in caplog.text + + async def test_gather_with_concurrency(): """Test gather_with_concurrency limits the number of running tasks."""