Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix thread safety reentry #203

Merged
merged 3 commits into from
Aug 30, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions requests_mock/mocker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
# License for the specific language governing permissions and limitations
# under the License.

import contextlib
import functools
import sys
import threading
import types

Expand All @@ -30,7 +32,26 @@

_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()


@contextlib.contextmanager
def threading_rlock(timeout):
kwargs = {}
if sys.version_info.major >= 3:
# python2 doesn't support the timeout argument
kwargs['timeout'] = timeout

if not _send_lock.acquire(**kwargs):
raise Exception("Could not acquire threading lock - possible deadlock scenario")

try:
yield
finally:
_send_lock.release()


def _is_bound_method(method):
Expand Down Expand Up @@ -134,8 +155,17 @@ 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:
with threading_rlock(timeout=10):
# mock get_adapter
#
# NOTE(phodge): requests.Session.send() is actually
# reentrant due to how it resolves redirects with nested
# calls to send(), however the reentry occurs _after_ the
# call to self.get_adapter(), so it doesn't matter that we
# will restore _last_get_adapter before a nested send() has
# completed as long as we monkeypatch get_adapter() each
# time immediately before calling original send() like we
# are doing here.
_set_method(session, "get_adapter", _fake_get_adapter)

# NOTE(jamielennox): self._last_send vs _original_send. Whilst it
Expand Down