| import threading | |
| import scipy._lib.decorator | |
| __all__ = ['ReentrancyError', 'ReentrancyLock', 'non_reentrant'] | |
| class ReentrancyError(RuntimeError): | |
| pass | |
| class ReentrancyLock: | |
| """ | |
| Threading lock that raises an exception for reentrant calls. | |
| Calls from different threads are serialized, and nested calls from the | |
| same thread result to an error. | |
| The object can be used as a context manager or to decorate functions | |
| via the decorate() method. | |
| """ | |
| def __init__(self, err_msg): | |
| self._rlock = threading.RLock() | |
| self._entered = False | |
| self._err_msg = err_msg | |
| def __enter__(self): | |
| self._rlock.acquire() | |
| if self._entered: | |
| self._rlock.release() | |
| raise ReentrancyError(self._err_msg) | |
| self._entered = True | |
| def __exit__(self, type, value, traceback): | |
| self._entered = False | |
| self._rlock.release() | |
| def decorate(self, func): | |
| def caller(func, *a, **kw): | |
| with self: | |
| return func(*a, **kw) | |
| return scipy._lib.decorator.decorate(func, caller) | |
| def non_reentrant(err_msg=None): | |
| """ | |
| Decorate a function with a threading lock and prevent reentrant calls. | |
| """ | |
| def decorator(func): | |
| msg = err_msg | |
| if msg is None: | |
| msg = f"{func.__name__} is not re-entrant" | |
| lock = ReentrancyLock(msg) | |
| return lock.decorate(func) | |
| return decorator | |