diff --git a/src/textual/constants.py b/src/textual/constants.py index 8cde416796..be9cdb5c0d 100644 --- a/src/textual/constants.py +++ b/src/textual/constants.py @@ -118,3 +118,8 @@ def _get_textual_animations() -> AnimationLevel: ESCAPE_DELAY: Final[float] = _get_environ_int("ESCDELAY", 100) / 1000.0 """The delay (in seconds) before reporting an escape key (not used if the extend key protocol is available).""" + +SLOW_THRESHOLD: int = _get_environ_int("TEXTUAL_SLOW_THRESHOLD", 500) +"""The time threshold (in milliseconds) after which a warning is logged +if message processing exceeds this duration. +""" diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 5231bfaaac..2374e616de 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -11,10 +11,12 @@ from __future__ import annotations import asyncio +import os import threading from asyncio import CancelledError, Queue, QueueEmpty, Task, create_task from contextlib import contextmanager from functools import partial +from time import perf_counter from typing import ( TYPE_CHECKING, Any, @@ -35,6 +37,7 @@ from ._context import prevent_message_types_stack from ._on import OnNoWidget from ._time import time +from .constants import SLOW_THRESHOLD from .css.match import match from .events import Event from .message import Message @@ -666,6 +669,16 @@ async def _dispatch_message(self, message: Message) -> None: # Allow apps to treat events and messages separately if isinstance(message, Event): await self.on_event(message) + elif "debug" in self.app.features: + start = perf_counter() + await self._on_message(message) + if perf_counter() - start > SLOW_THRESHOLD / 1000: + log.warning( + f"method=<{self.__class__.__name__}." + f"{message.handler_name}>", + f"Took over {SLOW_THRESHOLD}ms to process.", + "\nTo avoid screen freezes, consider using a worker.", + ) else: await self._on_message(message) if self._next_callbacks: