Source code for core_mixins.decorators.timeout_signal

# -*- coding: utf-8 -*-

"""
This decorator (alternative to `with_timeout`) ensures a function
does not run for more than a certain amount of seconds
before an error (`TimeoutError`) is raised.
"""

import functools
import platform
import signal
import threading
import typing


[docs] def with_timeout_signal( fcn: typing.Optional[typing.Callable] = None, timeout: float = 10, ) -> typing.Callable: """ It executes a function that will time out after the specified amount of seconds using `signal`. Could be an alternative for `with_timeout` because does not require spanning another process or the use of a queue. Caveats: - Only works on Unix/Linux systems (SIGALRM not available on Windows). - Must be called from the main thread (signal.alarm limitation). - Not re-entrant: nested or concurrent decorated functions will interfere. - Not compatible with async functions or async contexts. - May conflict with other code using SIGALRM. :param fcn: The function being decorated. :param timeout: The seconds before time out. :return: The wrapped function. :raises TimeoutError: If the decorated function exceeds the timeout duration. :raises RuntimeError: If used on Windows or from a non-main thread. """ if platform.system() == "Windows": raise RuntimeError( "The `with_timeout_signal` decorator is not supported " "on Windows. Use `with_timeout` decorator instead." ) def decorator_timeout(_fcn: typing.Callable): _fcn_name = getattr(_fcn, "__name__", repr(_fcn)) def timeout_handler(_signum, _frame): raise TimeoutError( f"Function '{_fcn_name}' timed out " f"after {timeout} seconds." ) @functools.wraps(_fcn) def wrapper(*args, **kwargs): if threading.current_thread() != threading.main_thread(): raise RuntimeError( f"`with_timeout_signal` can only be used from the " f"main thread. Function '{_fcn_name}' was called " f"from thread: '{threading.current_thread().name}'." ) signal.signal(signal.SIGALRM, timeout_handler) signal.setitimer(signal.ITIMER_REAL, timeout) try: return _fcn(*args, **kwargs) finally: signal.setitimer(signal.ITIMER_REAL, 0) return wrapper if not fcn: return decorator_timeout return decorator_timeout(fcn)