stopit: Timeout Control for Python
stopit is a Python library that provides flexible mechanisms for controlling the execution time of code blocks and functions. It offers both context managers and decorators to enforce timeouts and can raise exceptions in other threads. The current version is 1.1.2, with its last major update in 2018, indicating a maintenance-focused release cadence.
Common errors
-
AttributeError: module 'stopit' has no attribute 'Timeout'
cause Attempting to use the old class name `Timeout` for the context manager, which was renamed in version 1.1.0.fixChange `stopit.Timeout` to `stopit.ThreadingTimeout`. Similarly, `stopit.timeoutable` should be `stopit.threading_timeoutable`. -
Code inside `with stopit.ThreadingTimeout(...)` block doesn't stop immediately, especially with `time.sleep()`.
cause stopit's threading-based timeouts cannot interrupt Python's blocking atomic operations (like `time.sleep` or certain I/O calls) that hold the GIL. The exception is injected only when the GIL is released.fixIf the task includes `time.sleep`, break it into smaller sleeps or introduce manual checks for the timeout state. For blocking I/O, consider `multiprocessing` or asynchronous libraries if immediate termination is critical. -
stopit.TimeoutException: Timeout
cause This error can occur unexpectedly even with very small, non-zero timeouts, especially on Windows or when the timeout value is extremely low (e.g., 0.0000000001). It can indicate a race condition in the underlying threading implementation.fixIncrease the timeout duration slightly or avoid using extremely granular timeout values if possible. If the issue persists, review the context manager's `swallow_exc` parameter or handle the `TimeoutException` gracefully. -
TypeError: exceptions must derive from BaseException
cause If you try to catch a custom `TimeoutError` without defining it as an exception class derived from `Exception` (or `BaseException`).fixEnsure any custom exception types you use are properly defined: `class MyTimeoutError(Exception): pass`.
Warnings
- breaking In version 1.1.0, the main context manager `stopit.Timeout` was renamed to `stopit.ThreadingTimeout`, and the decorator `stopit.timeoutable` was renamed to `stopit.threading_timeoutable`. Using the old names will result in import errors or unexpected behavior.
- gotcha Signal-based timeout controls (`SignalTimeout` and `signal_timeoutable`) are not supported on Windows due to the operating system's lack of `signal.SIGALRM` support.
- gotcha Threading-based timeout mechanisms (like `ThreadingTimeout`) will only work reliably with CPython implementations. They rely on CPython-specific low-level APIs and are not compatible with alternative Python implementations like PyPy, Jython, or IronPython.
- gotcha The library cannot stop the execution of blocking Python atomic instructions that acquire the Global Interpreter Lock (GIL), such as `time.sleep()` for long durations or I/O operations. The asynchronous exception will only be effective *after* such blocking operations complete.
- gotcha Very short timeout values, especially zero, can lead to unexpected `TimeoutException` being raised immediately or trigger race conditions, particularly on Windows/Cygwin. The accuracy of timeouts generally depends on the GIL interval checking of your Python platform.
Install
-
pip install stopit
Imports
- ThreadingTimeout
from stopit import Timeout
from stopit import ThreadingTimeout
- threading_timeoutable
from stopit import timeoutable
from stopit import threading_timeoutable
- TimeoutException
from stopit import TimeoutException
- async_raise
from stopit import async_raise
Quickstart
import stopit
import time
print("Starting long running task with 5-second timeout...")
with stopit.ThreadingTimeout(5) as context_manager:
# Simulate a long-running operation
for i in range(10**8):
_ = i * 2 # Some computation
if not context_manager.state == context_manager.EXECUTING: # Optional: check state inside loop
break
if context_manager.state == context_manager.EXECUTED:
print("Task completed within timeout.")
elif context_manager.state == context_manager.TIMED_OUT:
print("Task timed out after 5 seconds.")
# Example using a decorator
@stopit.threading_timeoutable(default='Timed out!')
def potentially_long_function(timeout=1, max_iterations=10**8):
print(f" Function starting with {timeout}s timeout...")
for i in range(max_iterations):
_ = i * 2
time.sleep(0.001) # Small sleep to allow thread context switching
return "Function completed."
result = potentially_long_function(timeout=2)
print(f"Decorator result: {result}")
result_fast = potentially_long_function(timeout=10, max_iterations=1000)
print(f"Decorator result (fast completion): {result_fast}")