From 3f317f65042d45ffa92f18cf28ef72923c16d91e Mon Sep 17 00:00:00 2001 From: Peter Hodge Date: Fri, 22 Apr 2022 12:39:41 +1000 Subject: [PATCH] Change to use RLock and set a timeout to prevent infinite deadlocks --- requests_mock/mocker.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/requests_mock/mocker.py b/requests_mock/mocker.py index 3b787eb..91946ef 100644 --- a/requests_mock/mocker.py +++ b/requests_mock/mocker.py @@ -30,7 +30,10 @@ _original_send = requests.Session.send -_send_lock = threading.Lock() +# NOTE(phodge): we need to use an RLock (reentrant lock) here because +# requests.Session.send() is reentrant. See further comments where we +# monkeypatch get_adapter() +_send_lock = threading.RLock() def _is_bound_method(method): @@ -134,7 +137,10 @@ def _fake_send(session, request, **kwargs): # are multiple threads running - one thread could restore the # original get_adapter() just as a second thread is about to # execute _original_send() below - with _send_lock: + if not _send_lock.acquire(timeout=10): + raise Exception("Could not acquire threading lock - possible deadlock scenario") + + try: # mock get_adapter _set_method(session, "get_adapter", _fake_get_adapter) @@ -160,6 +166,8 @@ def _fake_send(session, request, **kwargs): finally: # restore get_adapter _set_method(session, "get_adapter", self._last_get_adapter) + finally: + _send_lock.release() # if we are here it means we must run the real http request # Or, with nested mocks, to the parent mock, that is why we use